using NeonSprawl.Server.Game.Encounters; namespace NeonSprawl.Server.Tests.Game.Encounters; public sealed class EncounterProgressOperationsTests { private const string PlayerId = "dev-local-1"; private const string EncounterId = "prototype_combat_pocket"; private const string MeleeNpc = "prototype_npc_melee"; private const string RangedNpc = "prototype_npc_ranged"; private const string EliteNpc = "prototype_npc_elite"; [Fact] public async Task TryActivateOnFirstEngagement_ShouldStartEncounter_WhenNpcIsRequired() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); // Act var activated = EncounterProgressOperations.TryActivateOnFirstEngagement( PlayerId, MeleeNpc, registry, progress, completion); // Assert Assert.True(activated); Assert.True(progress.TryGetProgress(PlayerId, EncounterId, out var snapshot)); Assert.True(snapshot.Started); Assert.Empty(snapshot.DefeatedNpcInstanceIds); Assert.False(EncounterProgressOperations.IsAllRequiredTargetsDefeated(PlayerId, EncounterId, registry, progress)); } [Fact] public async Task TryMarkTargetDefeated_ShouldReturnFalse_WhenNotActivated() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); // Act var marked = EncounterProgressOperations.TryMarkTargetDefeated( PlayerId, MeleeNpc, registry, progress, completion); // Assert Assert.False(marked); } [Fact] public async Task IsAllRequiredTargetsDefeated_ShouldBecomeTrue_AfterThirdDefeatInAnyOrder() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); _ = EncounterProgressOperations.TryActivateOnFirstEngagement(PlayerId, EliteNpc, registry, progress, completion); // Act _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, EliteNpc, registry, progress, completion); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, MeleeNpc, registry, progress, completion); var afterTwo = EncounterProgressOperations.IsAllRequiredTargetsDefeated(PlayerId, EncounterId, registry, progress); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, RangedNpc, registry, progress, completion); var afterThree = EncounterProgressOperations.IsAllRequiredTargetsDefeated(PlayerId, EncounterId, registry, progress); // Assert Assert.False(afterTwo); Assert.True(afterThree); } [Fact] public async Task TryMarkTargetDefeated_ShouldBeIdempotent_WhenNpcAlreadyDefeated() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); _ = EncounterProgressOperations.TryActivateOnFirstEngagement(PlayerId, MeleeNpc, registry, progress, completion); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, MeleeNpc, registry, progress, completion); // Act var secondMark = EncounterProgressOperations.TryMarkTargetDefeated( PlayerId, MeleeNpc, registry, progress, completion); // Assert Assert.True(secondMark); Assert.True(progress.TryGetProgress(PlayerId, EncounterId, out var snapshot)); Assert.Single(snapshot.DefeatedNpcInstanceIds); } [Fact] public async Task TryMarkTargetDefeated_ShouldReturnFalse_WhenEncounterAlreadyCompleted() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); _ = EncounterProgressOperations.TryActivateOnFirstEngagement(PlayerId, MeleeNpc, registry, progress, completion); _ = completion.TryMarkCompleted(PlayerId, EncounterId, DateTimeOffset.UtcNow); // Act var marked = EncounterProgressOperations.TryMarkTargetDefeated( PlayerId, MeleeNpc, registry, progress, completion); // Assert Assert.False(marked); } [Fact] public async Task TryActivateOnFirstEngagement_ShouldReturnFalse_WhenEncounterAlreadyCompleted() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); _ = completion.TryMarkCompleted(PlayerId, EncounterId, DateTimeOffset.UtcNow); // Act var activated = EncounterProgressOperations.TryActivateOnFirstEngagement( PlayerId, MeleeNpc, registry, progress, completion); // Assert Assert.False(activated); Assert.False(progress.TryGetProgress(PlayerId, EncounterId, out _)); } [Fact] public async Task TryActivateOnFirstEngagement_ShouldReturnFalse_WhenNpcNotInAnyEncounter() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); // Act var activated = EncounterProgressOperations.TryActivateOnFirstEngagement( PlayerId, "prototype_npc_unknown", registry, progress, completion); // Assert Assert.False(activated); } [Fact] public async Task IsAllRequiredTargetsDefeated_ShouldReturnTrue_WhenEncounterIdHasMixedCase() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); var completion = factory.Services.GetRequiredService(); _ = EncounterProgressOperations.TryActivateOnFirstEngagement(PlayerId, MeleeNpc, registry, progress, completion); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, MeleeNpc, registry, progress, completion); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, RangedNpc, registry, progress, completion); _ = EncounterProgressOperations.TryMarkTargetDefeated(PlayerId, EliteNpc, registry, progress, completion); // Act var allDefeated = EncounterProgressOperations.IsAllRequiredTargetsDefeated( PlayerId, " Prototype_Combat_Pocket ", registry, progress); // Assert Assert.True(allDefeated); } [Fact] public async Task IsAllRequiredTargetsDefeated_ShouldReturnTrue_WhenDefeatedSetIsSupersetOfRequired() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var registry = factory.Services.GetRequiredService(); var progress = factory.Services.GetRequiredService(); _ = progress.TryActivate(PlayerId, EncounterId); _ = progress.TryAddDefeatedTarget(PlayerId, EncounterId, MeleeNpc); _ = progress.TryAddDefeatedTarget(PlayerId, EncounterId, RangedNpc); _ = progress.TryAddDefeatedTarget(PlayerId, EncounterId, EliteNpc); _ = progress.TryAddDefeatedTarget(PlayerId, EncounterId, "extra_non_required_npc"); // Act var allDefeated = EncounterProgressOperations.IsAllRequiredTargetsDefeated( PlayerId, EncounterId, registry, progress); // Assert Assert.True(allDefeated); } [Fact] public async Task TryMarkCompleted_ShouldReturnFalseOnSecondAttempt_WhenUsingCompletionStoreDirectly() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); var completion = factory.Services.GetRequiredService(); var completedAt = new DateTimeOffset(2026, 5, 31, 12, 0, 0, TimeSpan.Zero); // Act var first = completion.TryMarkCompleted(PlayerId, EncounterId, completedAt); var second = completion.TryMarkCompleted(PlayerId, EncounterId, completedAt); // Assert Assert.True(first); Assert.False(second); } }