using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Time.Testing; using NeonSprawl.Server.Game.AbilityInput; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Game.Skills; using Npgsql; namespace NeonSprawl.Server.Tests.Game.Interaction; /// /// Same in-memory overrides as , plus a stub /// where salvage does not allow activity — for NEO-41 defensive 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."); builder.UseSetting("Content:SkillsDirectory", skillsDir); 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(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(); }); } } /// 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", new[] { "mission_reward" }); private static readonly SkillDefRow Refine = new( "refine", "process", "Refine", new[] { "activity", "mission_reward", "trainer" }); private static readonly SkillDefRow Intrusion = new( "intrusion", "tech", "Intrusion", new[] { "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; }