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.Mastery; using NeonSprawl.Server.Game.PositionState; namespace NeonSprawl.Server.Tests.Game.Skills; /// /// Same in-memory overrides as , plus a stub /// where refine does not allow activity — NEO-42 defensive tests without mutating disk catalog. /// public sealed class RefineActivityDeniedRegistryWebApplicationFactory : 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 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:RecipesDirectory", recipesDir); builder.UseSetting("Content:AbilitiesDirectory", abilitiesDir); builder.UseSetting("Content:NpcBehaviorsDirectory", npcBehaviorsDir); 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(IPlayerPerkStateStore) || 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(); }); } } /// Prototype trio ids; refine excludes activity so craft/refine grant path denies without throwing. internal sealed class RefineActivityDeniedSkillRegistry : ISkillDefinitionRegistry { private static readonly SkillDefRow Salvage = new( "salvage", "gather", "Salvage", ["activity", "mission_reward"]); private static readonly SkillDefRow RefineNoActivity = new( "refine", "process", "Refine", ["mission_reward", "trainer"]); private static readonly SkillDefRow Intrusion = new( "intrusion", "tech", "Intrusion", ["activity", "mission_reward", "book_or_item"]); private static readonly IReadOnlyList Ordered = [ Intrusion, RefineNoActivity, Salvage, ]; 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; }