using NeonSprawl.Server.Game.Contracts; using NeonSprawl.Server.Game.Encounters; using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.Items; using NeonSprawl.Server.Game.Quests; using NeonSprawl.Server.Game.Skills; namespace NeonSprawl.Server.Tests.Game.Contracts; public sealed class ContractEconomyValidationTests { private const string TemplateId = "prototype_contract_clear_combat_pocket"; private const string EncounterId = "prototype_combat_pocket"; private const string GridOperatorsFactionId = "prototype_faction_grid_operators"; [Fact] public void TryValidateTemplate_ShouldReturnTrue_WhenPrototypeFreezeRowAtCaps() { // Arrange var template = PrototypeE7M4ContractCatalogRules.ExpectedTemplateFreeze[TemplateId]; var registries = CreatePrototypeRegistries(); // Act var ok = ContractEconomyValidation.TryValidateTemplate( template, registries.EncounterRegistry, registries.ItemRegistry, registries.SkillRegistry, registries.FactionRegistry, out var reasonCode); // Assert Assert.True(ok); Assert.Null(reasonCode); } [Fact] public void TryValidateTemplate_ShouldDenyEconomyCapExceeded_WhenItemQuantityOverBandCap() { // Arrange var prototype = PrototypeE7M4ContractCatalogRules.ExpectedTemplateFreeze[TemplateId]; var overCapTemplate = prototype with { CompletionRewardBundle = new QuestRewardBundleRow( [new RewardGrantRow("scrap_metal_bulk", 11)], prototype.CompletionRewardBundle.SkillXpGrants, prototype.CompletionRewardBundle.ReputationGrants), }; var registries = CreatePrototypeRegistries(); // Act var ok = ContractEconomyValidation.TryValidateTemplate( overCapTemplate, registries.EncounterRegistry, registries.ItemRegistry, registries.SkillRegistry, registries.FactionRegistry, out var reasonCode); // Assert Assert.False(ok); Assert.Equal(ContractGeneratorReasonCodes.EconomyCapExceeded, reasonCode); } [Fact] public void TryValidateTemplate_ShouldDenyInvalidRewardBundle_WhenItemIdUnknown() { // Arrange var prototype = PrototypeE7M4ContractCatalogRules.ExpectedTemplateFreeze[TemplateId]; var unknownItemTemplate = prototype with { CompletionRewardBundle = new QuestRewardBundleRow( [new RewardGrantRow("unknown_item_xyz", 1)], prototype.CompletionRewardBundle.SkillXpGrants, prototype.CompletionRewardBundle.ReputationGrants), }; var registries = CreatePrototypeRegistries(); // Act var ok = ContractEconomyValidation.TryValidateTemplate( unknownItemTemplate, registries.EncounterRegistry, registries.ItemRegistry, registries.SkillRegistry, registries.FactionRegistry, out var reasonCode); // Assert Assert.False(ok); Assert.Equal(ContractGeneratorReasonCodes.InvalidRewardBundle, reasonCode); } private static CatalogRegistries CreatePrototypeRegistries() { var encounterRows = new Dictionary(StringComparer.Ordinal) { [EncounterId] = new( EncounterId, "Prototype Combat Pocket", "defeat_all_targets", ["prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite"], "prototype_combat_pocket_clear"), }; var encounterRegistry = new EncounterDefinitionRegistry( new EncounterDefinitionCatalog("/tmp/encounters", encounterRows, catalogJsonFileCount: 1)); var itemRows = new Dictionary(StringComparer.Ordinal) { ["scrap_metal_bulk"] = new( "scrap_metal_bulk", "Scrap Metal (Bulk)", "material", StackMax: 999, InventorySlotKind: "bag", Rarity: null, BindPolicy: null, DurabilityMax: null), }; var itemRegistry = new ItemDefinitionRegistry( new ItemDefinitionCatalog("/tmp/items", itemRows, catalogJsonFileCount: 1)); var skillRows = new Dictionary(StringComparer.Ordinal) { ["salvage"] = new("salvage", "gather", "Salvage", ["activity", "mission_reward"]), ["refine"] = new("refine", "process", "Refine", ["activity", "mission_reward", "trainer"]), ["intrusion"] = new("intrusion", "tech", "Intrusion", ["activity", "mission_reward", "book_or_item"]), }; var skillRegistry = new SkillDefinitionRegistry( new SkillDefinitionCatalog("/tmp/skills", skillRows, catalogJsonFileCount: 1)); var factionRows = new Dictionary(StringComparer.Ordinal) { [GridOperatorsFactionId] = PrototypeE7M3FactionCatalogRules.ExpectedFactionFreeze[GridOperatorsFactionId], }; var factionRegistry = new FactionDefinitionRegistry( new FactionDefinitionCatalog("/tmp/factions", factionRows, catalogJsonFileCount: 1)); return new CatalogRegistries(encounterRegistry, itemRegistry, skillRegistry, factionRegistry); } private sealed record CatalogRegistries( IEncounterDefinitionRegistry EncounterRegistry, IItemDefinitionRegistry ItemRegistry, ISkillDefinitionRegistry SkillRegistry, IFactionDefinitionRegistry FactionRegistry); }