using Microsoft.AspNetCore.Hosting; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Encounters; namespace NeonSprawl.Server.Tests.Game.Combat; public sealed class CombatTargetFixtureApiTests { private const string FixturePath = "/game/__dev/combat-targets-fixture"; [Fact] public async Task PostCombatTargetFixture_ShouldResetPrototypeTargets_WhenEnabled() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var store = factory.Services.GetRequiredService(); var threatStore = factory.Services.GetRequiredService(); var runtimeStore = factory.Services.GetRequiredService(); var playerHealthStore = factory.Services.GetRequiredService(); var progressStore = factory.Services.GetRequiredService(); var completionStore = factory.Services.GetRequiredService(); var eventStore = factory.Services.GetRequiredService(); _ = progressStore.TryActivate("dev-local-1", "prototype_combat_pocket"); _ = progressStore.TryAddDefeatedTarget("dev-local-1", "prototype_combat_pocket", "prototype_npc_melee"); _ = completionStore.TryMarkCompleted("dev-local-1", "prototype_combat_pocket", DateTimeOffset.UtcNow); _ = eventStore.TryRecord( new EncounterCompleteEvent( "dev-local-1", "prototype_combat_pocket", "prototype_combat_pocket_clear", [new EncounterGrantApplied("scrap_metal_bulk", 10)], DateTimeOffset.UtcNow, "dev-local-1:prototype_combat_pocket")); _ = store.TryApplyDamage(PrototypeNpcRegistry.PrototypeNpcMeleeId, 100, out _); _ = playerHealthStore.TryApplyDamage("dev-local-1", 40, out _); _ = threatStore.TrySetHolder(PrototypeNpcRegistry.PrototypeNpcMeleeId, "dev-local-1"); var windupStarted = new DateTimeOffset(2026, 5, 27, 12, 0, 0, TimeSpan.Zero); _ = runtimeStore.TryWrite( PrototypeNpcRegistry.PrototypeNpcMeleeId, new NpcRuntimeStateSnapshot( PrototypeNpcRegistry.PrototypeNpcMeleeId, NpcBehaviorState.TelegraphWindup, windupStarted, new ActiveTelegraphSnapshot("prototype_npc_melee:test", windupStarted))); // Act var response = await client.PostAsJsonAsync( FixturePath, new CombatTargetFixtureRequest { SchemaVersion = CombatTargetFixtureRequest.CurrentSchemaVersion, }); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.True(body!.Applied); store.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var melee); Assert.Equal(100, melee.CurrentHp); Assert.False(melee.Defeated); threatStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var threat); Assert.Null(threat.AggroHolderPlayerId); runtimeStore.TryGet(PrototypeNpcRegistry.PrototypeNpcMeleeId, out var runtime); Assert.Equal(NpcBehaviorState.Idle, runtime.BehaviorState); Assert.Null(runtime.ActiveTelegraph); playerHealthStore.TryGet("dev-local-1", out var playerHealth); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, playerHealth.CurrentHp); Assert.False(playerHealth.Defeated); Assert.False(progressStore.TryGetProgress("dev-local-1", "prototype_combat_pocket", out _)); Assert.False(completionStore.IsCompleted("dev-local-1", "prototype_combat_pocket")); Assert.False(eventStore.TryGet("dev-local-1", "prototype_combat_pocket", out _)); } [Fact] public async Task PostCombatTargetFixture_ShouldClearAbilityCooldowns_WhenEnabled() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); var cooldownStore = factory.Services.GetRequiredService(); var now = factory.FakeClock!.GetUtcNow(); cooldownStore.StartCooldown("dev-local-1", slotIndex: 0, now, TimeSpan.FromSeconds(30)); Assert.True(cooldownStore.IsOnCooldown("dev-local-1", slotIndex: 0, now)); // Act var response = await client.PostAsJsonAsync( FixturePath, new CombatTargetFixtureRequest { SchemaVersion = CombatTargetFixtureRequest.CurrentSchemaVersion, }); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.False(cooldownStore.IsOnCooldown("dev-local-1", slotIndex: 0, now)); } [Fact] public async Task PostCombatTargetFixture_ShouldReturnNotFound_WhenRouteNotRegistered() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.WithWebHostBuilder(b => { b.UseEnvironment("Production"); b.UseSetting("Game:EnableCombatTargetFixtureApi", "false"); }).CreateClient(); // Act var response = await client.PostAsJsonAsync( FixturePath, new CombatTargetFixtureRequest { SchemaVersion = CombatTargetFixtureRequest.CurrentSchemaVersion, }); // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } }