neon-sprawl/server/NeonSprawl.Server.Tests/InMemoryWebApplicationFacto...

127 lines
8.3 KiB
C#

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.Combat;
using NeonSprawl.Server.Game.Crafting;
using NeonSprawl.Server.Game.Encounters;
using NeonSprawl.Server.Game.Factions;
using NeonSprawl.Server.Game.Gathering;
using NeonSprawl.Server.Game.Gigs;
using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Game.Quests;
using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.Npc;
using NeonSprawl.Server.Game.Skills;
using Npgsql;
namespace NeonSprawl.Server.Tests;
/// <summary>Forces the in-memory position store by replacing Postgres registrations after the host builds services (overrides process env e.g. CI <c>ConnectionStrings__NeonSprawl</c>).</summary>
public sealed class InMemoryWebApplicationFactory : WebApplicationFactory<Program>
{
/// <summary>Controllable UTC clock shared by cast + cooldown routes in tests (NEO-32).</summary>
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.");
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.");
var rewardTablesDir = RewardTableCatalogPathResolution.TryDiscoverRewardTablesDirectory(AppContext.BaseDirectory)
?? throw new InvalidOperationException(
"Could not discover repo content/reward-tables from AppContext.BaseDirectory; run tests from the neon-sprawl clone.");
var encountersDir = EncounterCatalogPathResolution.TryDiscoverEncountersDirectory(AppContext.BaseDirectory)
?? throw new InvalidOperationException(
"Could not discover repo content/encounters from AppContext.BaseDirectory; run tests from the neon-sprawl clone.");
var questsDir = QuestCatalogPathResolution.TryDiscoverQuestsDirectory(AppContext.BaseDirectory)
?? throw new InvalidOperationException(
"Could not discover repo content/quests from AppContext.BaseDirectory; run tests from the neon-sprawl clone.");
var factionsDir = FactionCatalogPathResolution.TryDiscoverFactionsDirectory(AppContext.BaseDirectory)
?? throw new InvalidOperationException(
"Could not discover repo content/factions 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("Content:RewardTablesDirectory", rewardTablesDir);
builder.UseSetting("Content:EncountersDirectory", encountersDir);
builder.UseSetting("Content:FactionsDirectory", factionsDir);
builder.UseSetting("Content:QuestsDirectory", questsDir);
builder.UseSetting("Game:EnableMasteryFixtureApi", "true");
builder.UseSetting("Game:EnableQuestFixtureApi", "true");
builder.UseSetting("Game:EnableResourceNodeFixtureApi", "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(IPlayerGigProgressionStore) ||
d.ServiceType == typeof(IPlayerQuestStateStore) ||
d.ServiceType == typeof(IFactionStandingStore) ||
d.ServiceType == typeof(IReputationDeltaStore) ||
d.ServiceType == typeof(IPlayerInventoryStore) ||
d.ServiceType == typeof(IResourceNodeInstanceStore) ||
d.ServiceType == typeof(IPlayerPerkStateStore) ||
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<TimeProvider>(fakeTime);
services.AddSingleton<IPositionStateStore, InMemoryPositionStateStore>();
services.AddSingleton<IPlayerHotbarLoadoutStore, InMemoryPlayerHotbarLoadoutStore>();
services.AddSingleton<IPlayerSkillProgressionStore, InMemoryPlayerSkillProgressionStore>();
services.AddSingleton<IPlayerGigProgressionStore, InMemoryPlayerGigProgressionStore>();
services.AddSingleton<IPlayerQuestStateStore, InMemoryPlayerQuestStateStore>();
services.AddSingleton<IFactionStandingStore, InMemoryFactionStandingStore>();
services.AddSingleton<IReputationDeltaStore, InMemoryReputationDeltaStore>();
services.AddSingleton<IPlayerInventoryStore, InMemoryPlayerInventoryStore>();
services.AddSingleton<IResourceNodeInstanceStore, InMemoryResourceNodeInstanceStore>();
services.AddSingleton<IPlayerPerkStateStore, InMemoryPlayerPerkStateStore>();
services.AddSingleton<IPlayerAbilityCooldownStore, InMemoryPlayerAbilityCooldownStore>();
});
}
}