using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Time.Testing; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.Combat; using NeonSprawl.Server.Game.Crafting; using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.Gathering; using NeonSprawl.Server.Game.Items; using NeonSprawl.Server.Game.Mastery; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Quests; namespace NeonSprawl.Server.Tests.Game.Interaction; /// /// Same in-memory overrides as , plus a stub /// where salvage does not allow activity — for gather deny tests without mutating disk catalog. /// public sealed class SalvageActivityDeniedRegistryWebApplicationFactory : WebApplicationFactory { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Testing"); var skillsDir = SkillCatalogPathResolution.TryDiscoverSkillsDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/skills from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var masteryDir = MasteryCatalogPathResolution.TryDiscoverMasteryDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/mastery from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var itemsDir = ItemCatalogPathResolution.TryDiscoverItemsDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/items from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var resourceNodesDir = ResourceNodeCatalogPathResolution.TryDiscoverResourceNodesDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/resource-nodes from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var recipesDir = RecipeCatalogPathResolution.TryDiscoverRecipesDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/recipes from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var abilitiesDir = AbilityCatalogPathResolution.TryDiscoverAbilitiesDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/abilities from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); var npcBehaviorsDir = NpcBehaviorCatalogPathResolution.TryDiscoverNpcBehaviorsDirectory(AppContext.BaseDirectory) ?? throw new InvalidOperationException( "Could not discover repo content/npc-behaviors from AppContext.BaseDirectory; run tests from the neon-sprawl clone."); builder.UseSetting("Content:SkillsDirectory", skillsDir); builder.UseSetting("Content:MasteryDirectory", masteryDir); builder.UseSetting("Content:ItemsDirectory", itemsDir); builder.UseSetting("Content:ResourceNodesDirectory", resourceNodesDir); builder.UseSetting("Content:RecipesDirectory", recipesDir); builder.UseSetting("Content:AbilitiesDirectory", abilitiesDir); builder.UseSetting("Content:NpcBehaviorsDirectory", npcBehaviorsDir); builder.UseSetting("Game:EnableMasteryFixtureApi", "true"); builder.ConfigureTestServices(services => { for (var i = services.Count - 1; i >= 0; i--) { var d = services[i]; if (d.ServiceType == typeof(IPositionStateStore) || d.ServiceType == typeof(IPlayerHotbarLoadoutStore) || d.ServiceType == typeof(IPlayerSkillProgressionStore) || d.ServiceType == typeof(IPlayerInventoryStore) || d.ServiceType == typeof(IResourceNodeInstanceStore) || d.ServiceType == typeof(IPlayerPerkStateStore) || d.ServiceType == typeof(IPlayerQuestStateStore) || d.ServiceType == typeof(IFactionStandingStore) || d.ServiceType == typeof(IReputationDeltaStore) || d.ServiceType == typeof(TimeProvider) || d.ServiceType == typeof(IPlayerAbilityCooldownStore) || d.ServiceType == typeof(ISkillDefinitionRegistry) || d.ServiceType == typeof(NpgsqlDataSource) || (d.ServiceType == typeof(IHostedService) && (d.ImplementationType == typeof(PostgresDevPlayerSeedHostedService) || d.ImplementationType == typeof(NpgsqlDataSourceShutdownHostedService)))) { services.RemoveAt(i); } } var fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 4, 30, 12, 0, 0, TimeSpan.Zero)); services.AddSingleton(fakeTime); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); }); } } /// Prototype trio ids; salvage excludes activity so gather interact grant path denies without throwing. internal sealed class SalvageActivityDeniedSkillRegistry : ISkillDefinitionRegistry { private static readonly SkillDefRow SalvageNoActivity = new( "salvage", "gather", "Salvage", ["mission_reward"]); private static readonly SkillDefRow Refine = new( "refine", "process", "Refine", ["activity", "mission_reward", "trainer"]); private static readonly SkillDefRow Intrusion = new( "intrusion", "tech", "Intrusion", ["activity", "mission_reward", "book_or_item"]); private static readonly IReadOnlyList Ordered = [ Intrusion, Refine, SalvageNoActivity, ]; public bool TryGetDefinition(string? skillId, [NotNullWhen(true)] out SkillDefRow? definition) { if (skillId is null) { definition = null; return false; } var k = skillId.Trim(); foreach (var row in Ordered) { if (string.Equals(row.Id, k, StringComparison.OrdinalIgnoreCase)) { definition = row; return true; } } definition = null; return false; } public IReadOnlyList GetDefinitionsInIdOrder() => Ordered; }