using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Quests; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Quests; public class QuestDefinitionRegistryTests { private const string GatherIntroId = "prototype_quest_gather_intro"; private const string OperatorChainId = "prototype_quest_operator_chain"; public static TheoryData FrozenQuestIds => new(PrototypeE7M1QuestCatalogRules.ExpectedQuestIds.Order(StringComparer.Ordinal)); private static QuestDefinitionRegistry CreateRegistryFromRows(IReadOnlyDictionary byId) { var catalog = new QuestDefinitionCatalog("/tmp/quests", byId, catalogJsonFileCount: 1); return new QuestDefinitionRegistry(catalog); } private static QuestDefRow CreateGatherIntroRow() => new( GatherIntroId, "Intro: Salvage Run", [], [ new QuestStepDefRow( "gather_intro_step_salvage", "Gather scrap metal", [ new QuestObjectiveDefRow( "gather_intro_obj_scrap", "gather_item", "scrap_metal_bulk", 3, null, null), ]), ]); [Theory] [MemberData(nameof(FrozenQuestIds))] public void TryGetDefinition_ShouldReturnTrue_WhenFrozenQuestIdExists(string questId) { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [questId] = new QuestDefRow(questId, "Test Quest", [], []), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(questId, out var def); // Assert Assert.True(found); Assert.NotNull(def); Assert.Equal(questId, def!.Id); Assert.Equal("Test Quest", def.DisplayName); } [Fact] public void TryGetDefinition_ShouldReturnTrueAndExpectedMetadata_WhenGatherIntroExists() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(GatherIntroId, out var def); // Assert Assert.True(found); Assert.NotNull(def); Assert.Equal("Intro: Salvage Run", def!.DisplayName); Assert.Single(def.Steps); Assert.Equal("gather_intro_step_salvage", def.Steps[0].Id); } [Fact] public void TryGetDefinition_ShouldReturnTrueAndFourSteps_WhenOperatorChainExists() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [OperatorChainId] = new QuestDefRow( OperatorChainId, "Operator Chain", ["prototype_quest_gather_intro"], [ new QuestStepDefRow("chain_step_gather", "Bulk salvage", []), new QuestStepDefRow("chain_step_refine", "Refine stock", []), new QuestStepDefRow("chain_step_stim", "Craft field stim", []), new QuestStepDefRow("chain_step_token", "Hand off contract token", []), ]), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(OperatorChainId, out var def); // Assert Assert.True(found); Assert.NotNull(def); Assert.Equal(4, def!.Steps.Count); Assert.Equal("chain_step_token", def.Steps[3].Id); } [Fact] public void TryGetDefinition_ShouldReturnFalse_WhenQuestIdIsNull() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; 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_WhenQuestIdIsEmpty() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition(string.Empty, out var def); // Assert Assert.False(found); Assert.Null(def); } [Fact] public void TryGetDefinition_ShouldReturnFalse_WhenIdUnknown() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryGetDefinition("not_a_real_quest", out var def); // Assert Assert.False(found); Assert.Null(def); } [Fact] public void TryNormalizeKnown_ShouldReturnTrueAndLowercaseId_WhenInputHasWhitespaceAndMixedCase() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; var registry = CreateRegistryFromRows(rows); // Act var found = registry.TryNormalizeKnown(" Prototype_Quest_Gather_Intro ", out var normalized); // Assert Assert.True(found); Assert.Equal(GatherIntroId, normalized); } [Fact] public void TryNormalizeKnown_ShouldReturnFalse_WhenInputIsNull() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { [GatherIntroId] = CreateGatherIntroRow(), }; 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) { [GatherIntroId] = CreateGatherIntroRow(), }; 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) { [GatherIntroId] = CreateGatherIntroRow(), }; 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_WhenMultipleQuests() { // Arrange var rows = new Dictionary(StringComparer.Ordinal) { ["z_quest"] = new QuestDefRow("z_quest", "Z", [], []), [GatherIntroId] = CreateGatherIntroRow(), ["a_quest"] = new QuestDefRow("a_quest", "A", [], []), }; var registry = CreateRegistryFromRows(rows); // Act var list = registry.GetDefinitionsInIdOrder(); // Assert Assert.Equal(3, list.Count); Assert.Equal("a_quest", list[0].Id); Assert.Equal(GatherIntroId, list[1].Id); Assert.Equal("z_quest", list[2].Id); } [Fact] public async Task Host_ShouldResolveRegistryFromDi_WhenStartupSucceeds() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); // Act var registry = factory.Services.GetRequiredService(); var gatherFound = registry.TryGetDefinition(GatherIntroId, out var gather); var unknown = registry.TryGetDefinition("not_a_real_quest", out var missing); var normalized = registry.TryNormalizeKnown(" Prototype_Quest_Gather_Intro ", out var questId); var unknownNormalized = registry.TryNormalizeKnown("prototype_unknown", out var unknownQuestId); var list = registry.GetDefinitionsInIdOrder(); // Assert Assert.True(gatherFound); Assert.NotNull(gather); Assert.Equal("Intro: Salvage Run", gather!.DisplayName); Assert.False(unknown); Assert.Null(missing); Assert.True(normalized); Assert.Equal(GatherIntroId, questId); Assert.False(unknownNormalized); Assert.Equal("prototype_unknown", unknownQuestId); Assert.Equal(4, list.Count); Assert.Equal(PrototypeE7M1QuestCatalogRules.ExpectedQuestIds.Order(StringComparer.Ordinal), list.Select(q => q.Id)); foreach (var expectedId in PrototypeE7M1QuestCatalogRules.ExpectedQuestIds) { Assert.True(registry.TryGetDefinition(expectedId, out var def)); Assert.NotNull(def); Assert.False(string.IsNullOrWhiteSpace(def!.DisplayName)); } } }