using System.Net; using System.Net.Http.Json; using System.Text; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; 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); } }