using System.Net; using System.Net.Http.Json; using System.Text; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.Quests; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Quests; public sealed class QuestFixtureApiTests { private const string DevPlayer = "dev-local-1"; private const string FixturePath = $"/game/players/{DevPlayer}/__dev/quest-fixture"; private const string ChainQuestId = PrototypeE7M1QuestCatalogRules.ChainQuestId; private static QuestFixtureRequest ValidFixture(params string[] completedQuestIds) => new() { SchemaVersion = QuestFixtureRequest.CurrentSchemaVersion, CompletedQuestIds = completedQuestIds, }; [Fact] public async Task PostQuestFixture_ShouldReturnNotFound_WhenRouteNotRegistered() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.WithWebHostBuilder(b => { b.UseEnvironment("Production"); b.UseSetting("Game:EnableQuestFixtureApi", "false"); }).CreateClient(); // Act var response = await client.PostAsJsonAsync(FixturePath, ValidFixture(ChainQuestId)); // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public async Task PostQuestFixture_ShouldReturnBadRequest_WhenBodyNull() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); // Act var response = await client.PostAsync( FixturePath, new StringContent(string.Empty, Encoding.UTF8, "application/json")); // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] public async Task PostQuestFixture_ShouldReturnBadRequest_WhenSchemaVersionMismatch() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var request = new QuestFixtureRequest { SchemaVersion = QuestFixtureRequest.CurrentSchemaVersion + 99, CompletedQuestIds = [ChainQuestId], }; // Act var response = await client.PostAsJsonAsync(FixturePath, request); // Assert Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] public async Task PostQuestFixture_ShouldReturnNotFound_WhenPlayerUnknown() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); // Act var response = await client.PostAsJsonAsync( "/game/players/missing-player/__dev/quest-fixture", ValidFixture(ChainQuestId)); // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public async Task PostQuestFixture_ShouldMarkOperatorChainCompleted_WhenNotStarted() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var progressStore = factory.Services.GetRequiredService(); // Act var response = await client.PostAsJsonAsync(FixturePath, ValidFixture(ChainQuestId)); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.True(body!.Applied); Assert.True(progressStore.TryGetProgress(DevPlayer, ChainQuestId, out var progress)); Assert.Equal(QuestProgressStatus.Completed, progress.Status); Assert.NotNull(progress.CompletedAt); } [Fact] public async Task PostQuestFixture_ShouldBeIdempotent_WhenQuestAlreadyCompleted() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var first = await client.PostAsJsonAsync(FixturePath, ValidFixture(ChainQuestId)); Assert.Equal(HttpStatusCode.OK, first.StatusCode); // Act var second = await client.PostAsJsonAsync(FixturePath, ValidFixture(ChainQuestId)); // Assert Assert.Equal(HttpStatusCode.OK, second.StatusCode); } [Fact] public async Task PostQuestFixture_ShouldClearProgress_WhenResetQuestIdsProvided() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var progressStore = factory.Services.GetRequiredService(); var markCompleted = await client.PostAsJsonAsync(FixturePath, ValidFixture(ChainQuestId)); Assert.Equal(HttpStatusCode.OK, markCompleted.StatusCode); Assert.True(progressStore.TryGetProgress(DevPlayer, ChainQuestId, out _)); // Act var reset = await client.PostAsJsonAsync( FixturePath, new QuestFixtureRequest { SchemaVersion = QuestFixtureRequest.CurrentSchemaVersion, ResetQuestIds = [ChainQuestId], }); // Assert Assert.Equal(HttpStatusCode.OK, reset.StatusCode); Assert.False(progressStore.TryGetProgress(DevPlayer, ChainQuestId, out _)); } [Fact] public async Task PostQuestFixture_ResetAllPrototypeQuests_ShouldReturnNotStartedOnGetQuestProgress() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var markCompleted = await client.PostAsJsonAsync( FixturePath, ValidFixture(ChainQuestId, PrototypeE7M1QuestCatalogRules.GatherIntroQuestId)); Assert.Equal(HttpStatusCode.OK, markCompleted.StatusCode); // Act var reset = await client.PostAsJsonAsync( FixturePath, new QuestFixtureRequest { SchemaVersion = QuestFixtureRequest.CurrentSchemaVersion, ResetQuestIds = [ PrototypeE7M1QuestCatalogRules.CombatIntroQuestId, PrototypeE7M1QuestCatalogRules.GatherIntroQuestId, PrototypeE7M1QuestCatalogRules.GridContractQuestId, PrototypeE7M1QuestCatalogRules.ChainQuestId, PrototypeE7M1QuestCatalogRules.RefineIntroQuestId, ], }); var progress = await client.GetAsync($"/game/players/{DevPlayer}/quest-progress"); // Assert Assert.Equal(HttpStatusCode.OK, reset.StatusCode); Assert.Equal(HttpStatusCode.OK, progress.StatusCode); var body = await progress.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.All(body!.Quests, row => Assert.Equal(QuestProgressApi.StatusNotStarted, row.Status)); } [Fact] public async Task PostQuestFixture_ShouldClearFactionStanding_WhenResetFactionIdsProvided() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var standingStore = factory.Services.GetRequiredService(); const string gridFactionId = PrototypeE7M3QuestFactionRules.GridContractGateFactionId; var apply = ReputationOperations.TryApplyDelta( DevPlayer, gridFactionId, 15, "fixture-seed-standing", ReputationDeltaSourceKinds.QuestCompletion, ChainQuestId, standingStore, factory.Services.GetRequiredService(), factory.Services.GetRequiredService()); Assert.True(apply.Success); Assert.Equal(15, standingStore.TryGetStanding(DevPlayer, gridFactionId).Standing); // Act var reset = await client.PostAsJsonAsync( FixturePath, new QuestFixtureRequest { SchemaVersion = QuestFixtureRequest.CurrentSchemaVersion, ResetFactionIds = [gridFactionId], }); // Assert Assert.Equal(HttpStatusCode.OK, reset.StatusCode); Assert.Equal(0, standingStore.TryGetStanding(DevPlayer, gridFactionId).Standing); } }