using System.Net; using System.Net.Http.Json; using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Npc; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Targeting; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Combat; public sealed class PlayerCombatHealthApiTests { private static async Task MoveDevPlayerNearEliteAsync(HttpClient client) { var response = await client.PostAsJsonAsync( "/game/players/dev-local-1/move", new MoveCommandRequest { SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, Target = new PositionVector { X = -2, Y = 0.9, Z = -2 }, }); response.EnsureSuccessStatusCode(); } private static async Task BindSlot0PulseAsync(HttpClient client) { var post = await client.PostAsJsonAsync( "/game/players/dev-local-1/hotbar-loadout", new HotbarLoadoutUpdateRequest { SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion, Slots = [new HotbarSlotBindingJson { SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse }], }); post.EnsureSuccessStatusCode(); } private static async Task LockPrototypeNpcEliteAsync(HttpClient client) { var response = await client.PostAsJsonAsync( "/game/players/dev-local-1/target/select", new TargetSelectRequest { SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, TargetId = PrototypeNpcRegistry.PrototypeNpcEliteId, }); response.EnsureSuccessStatusCode(); } private static AbilityCastRequest PulseCastRequest() => new() { SchemaVersion = AbilityCastRequest.CurrentSchemaVersion, SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse, TargetId = PrototypeNpcRegistry.PrototypeNpcEliteId, }; [Fact] public async Task GetPlayerCombatHealth_ShouldReturnSchemaV1_WithFullHpBaseline() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); // Act var response = await client.GetAsync("/game/players/dev-local-1/combat-health"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadFromJsonAsync(); Assert.NotNull(body); Assert.Equal(PlayerCombatHealthResponse.CurrentSchemaVersion, body!.SchemaVersion); Assert.Equal("dev-local-1", body.PlayerId); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, body.MaxHp); Assert.Equal(PlayerCombatHealthDefaults.MaxHp, body.CurrentHp); Assert.False(body.Defeated); } [Fact] public async Task GetPlayerCombatHealth_ShouldReturnNotFound_WhenPlayerUnknown() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); // Act var response = await client.GetAsync("/game/players/missing-player/combat-health"); // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public async Task GetPlayerCombatHealth_ShouldDecreaseAfterEliteTelegraphComplete() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var client = factory.CreateClient(); Assert.NotNull(factory.FakeClock); await BindSlot0PulseAsync(client); await MoveDevPlayerNearEliteAsync(client); await LockPrototypeNpcEliteAsync(client); var castResponse = await client.PostAsJsonAsync( "/game/players/dev-local-1/ability-cast", PulseCastRequest()); castResponse.EnsureSuccessStatusCode(); _ = await client.GetAsync("/game/world/npc-runtime-snapshot"); factory.FakeClock!.Advance(TimeSpan.FromSeconds(5)); _ = await client.GetAsync("/game/world/npc-runtime-snapshot"); factory.FakeClock.Advance(TimeSpan.FromSeconds(2.5)); // Act var snapshotResponse = await client.GetAsync("/game/world/npc-runtime-snapshot"); var healthResponse = await client.GetAsync("/game/players/dev-local-1/combat-health"); // Assert snapshotResponse.EnsureSuccessStatusCode(); healthResponse.EnsureSuccessStatusCode(); var snapshotBody = await snapshotResponse.Content.ReadFromJsonAsync(); var healthBody = await healthResponse.Content.ReadFromJsonAsync(); Assert.NotNull(snapshotBody); Assert.NotNull(healthBody); var elite = snapshotBody!.NpcInstances.Single(row => row.NpcInstanceId == PrototypeNpcRegistry.PrototypeNpcEliteId); Assert.Equal("recover", elite.State); Assert.Equal(75, healthBody!.CurrentHp); Assert.False(healthBody.Defeated); Assert.Equal("dev-local-1", healthBody.PlayerId); var store = factory.Services.GetRequiredService(); store.TryGet("dev-local-1", out var storeSnapshot); Assert.Equal(healthBody.CurrentHp, storeSnapshot.CurrentHp); Assert.Equal(healthBody.MaxHp, storeSnapshot.MaxHp); Assert.Equal(healthBody.Defeated, storeSnapshot.Defeated); } }