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; /// Forces the in-memory position store by replacing Postgres registrations after the host builds services (overrides process env e.g. CI ConnectionStrings__NeonSprawl). public sealed class InMemoryWebApplicationFactory : WebApplicationFactory { /// Controllable UTC clock shared by cast + cooldown routes in tests (NEO-32). public FakeTimeProvider? FakeClock { get; private set; } protected override void ConfigureWebHost(IWebHostBuilder builder) { // Avoid Development + DeveloperExceptionPage: invalid JSON / binding failures must stay 400 for API tests, // and Release CI runs rely on deterministic status codes (e.g. HotbarLoadoutApiTests malformed-body case). 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(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)); FakeClock = fakeTime; services.AddSingleton(fakeTime); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); }); } }