using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Encounters; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Encounters; public class EncounterDefinitionRegistryTests { private const string PrototypeCombatPocket = "prototype_combat_pocket"; private static EncounterDefinitionRegistry CreateRegistryFromRows(IReadOnlyDictionary byId) { var catalog = new EncounterDefinitionCatalog("/tmp/encounters", byId, catalogJsonFileCount: 1); return new EncounterDefinitionRegistry(catalog); } private static EncounterDefRow CreatePrototypeCombatPocketRow() => new( PrototypeCombatPocket, "Prototype Combat Pocket", "defeat_all_targets", ["prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite"], "prototype_combat_pocket_clear"); [Fact] public void TryGetDefinition_ShouldReturnTrueAndExpectedMetadata_WhenIdExists() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(PrototypeCombatPocket, out var def); // Assert Assert.True(found); Assert.NotNull(def); Assert.Equal("Prototype Combat Pocket", def!.DisplayName); Assert.Equal("defeat_all_targets", def.CompletionCriteriaKind); Assert.Equal("prototype_combat_pocket_clear", def.RewardTableId); Assert.Equal(3, def.RequiredNpcInstanceIds.Count); Assert.Contains("prototype_npc_melee", def.RequiredNpcInstanceIds); } [Fact] public void TryGetDefinition_ShouldReturnFalse_WhenEncounterIdIsNull() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(null, out var def); // Assert Assert.False(found); Assert.Null(def); } [Fact] public void TryGetDefinition_ShouldReturnFalse_WhenIdUnknown() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition("not_a_real_encounter", out var def); // Assert Assert.False(found); Assert.Null(def); } [Fact] public void TryNormalizeKnown_ShouldReturnTrueAndLowercaseId_WhenInputHasWhitespaceAndMixedCase() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryNormalizeKnown(" Prototype_Combat_Pocket ", out var normalized); // Assert Assert.True(found); Assert.Equal(PrototypeCombatPocket, normalized); } [Fact] public void TryNormalizeKnown_ShouldReturnFalse_WhenInputIsNull() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryNormalizeKnown(null, out var normalized); // Assert Assert.False(found); Assert.Equal(string.Empty, normalized); } [Fact] public void TryNormalizeKnown_ShouldReturnFalse_WhenInputIsWhitespaceOnly() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryNormalizeKnown(" ", out var normalized); // Assert Assert.False(found); Assert.Equal(string.Empty, normalized); } [Fact] public void TryNormalizeKnown_ShouldReturnFalse_WhenIdNotInCatalog() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryNormalizeKnown("prototype_unknown", out var normalized); // Assert Assert.False(found); Assert.Equal("prototype_unknown", normalized); } [Fact] public void GetDefinitionsInIdOrder_ShouldListAllRowsOrderedById_WhenMultipleEncounters() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { ["z_encounter"] = new EncounterDefRow( "z_encounter", "Z", "defeat_all_targets", ["prototype_npc_melee"], "prototype_combat_pocket_clear"), [PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(), ["a_encounter"] = new EncounterDefRow( "a_encounter", "A", "defeat_all_targets", ["prototype_npc_melee"], "prototype_combat_pocket_clear"), }; var registry = CreateRegistryFromRows(rows); // Act var list = registry.GetDefinitionsInIdOrder(); // Assert Assert.Equal(3, list.Count); Assert.Equal("a_encounter", list[0].Id); Assert.Equal(PrototypeCombatPocket, list[1].Id); Assert.Equal("z_encounter", list[2].Id); } [Fact] public async Task Host_ShouldResolveRegistriesFromDi_WhenStartupSucceeds() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); // Act var encounterRegistry = factory.Services.GetRequiredService(); var rewardRegistry = factory.Services.GetRequiredService(); var encounterFound = encounterRegistry.TryGetDefinition(PrototypeCombatPocket, out var encounter); var unknownEncounter = encounterRegistry.TryGetDefinition("not_a_real_encounter", out var missingEncounter); var normalizedEncounter = encounterRegistry.TryNormalizeKnown(" Prototype_Combat_Pocket ", out var encounterId); var rewardFound = rewardRegistry.TryGetDefinition("prototype_combat_pocket_clear", out var rewardTable); var unknownReward = rewardRegistry.TryNormalizeKnown("prototype_unknown", out var unknownRewardId); var encounterList = encounterRegistry.GetDefinitionsInIdOrder(); // Assert Assert.True(encounterFound); Assert.NotNull(encounter); Assert.Equal("prototype_combat_pocket_clear", encounter!.RewardTableId); Assert.False(unknownEncounter); Assert.Null(missingEncounter); Assert.True(normalizedEncounter); Assert.Equal(PrototypeCombatPocket, encounterId); Assert.True(rewardFound); Assert.NotNull(rewardTable); Assert.Equal(10, rewardTable!.FixedGrants.First(g => g.ItemId == "scrap_metal_bulk").Quantity); Assert.False(unknownReward); Assert.Equal("prototype_unknown", unknownRewardId); Assert.Single(encounterList); Assert.Equal(PrototypeCombatPocket, encounterList[0].Id); } [Fact] public async Task Host_ShouldResolveEncounterProgressStoresFromDi_WhenStartupSucceeds() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); var progressStore = factory.Services.GetRequiredService(); var completionStore = factory.Services.GetRequiredService(); var completeEventStore = factory.Services.GetRequiredService(); // Act var activated = progressStore.TryActivate("dev-local-1", PrototypeCombatPocket); var completed = completionStore.TryMarkCompleted( "dev-local-1", PrototypeCombatPocket, new DateTimeOffset(2026, 5, 31, 12, 0, 0, TimeSpan.Zero)); // Assert Assert.True(activated); Assert.True(completed); Assert.True(completionStore.IsCompleted("dev-local-1", PrototypeCombatPocket)); Assert.IsType(completeEventStore); } }