neon-sprawl/server/NeonSprawl.Server.Tests/Game/Skills/RefineActivityDeniedRegistr...

125 lines
5.3 KiB
C#

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.Crafting;
using NeonSprawl.Server.Game.Mastery;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Game.Skills;
using Npgsql;
namespace NeonSprawl.Server.Tests.Game.Skills;
/// <summary>
/// Same in-memory overrides as <see cref="InMemoryWebApplicationFactory"/>, plus a stub <see cref="ISkillDefinitionRegistry"/>
/// where <c>refine</c> does not allow <c>activity</c> — NEO-42 defensive tests without mutating disk catalog.
/// </summary>
public sealed class RefineActivityDeniedRegistryWebApplicationFactory : WebApplicationFactory<Program>
{
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.");
builder.UseSetting("Content:SkillsDirectory", skillsDir);
builder.UseSetting("Content:MasteryDirectory", masteryDir);
builder.UseSetting("Content:RecipesDirectory", recipesDir);
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<TimeProvider>(fakeTime);
services.AddSingleton<IPositionStateStore, InMemoryPositionStateStore>();
services.AddSingleton<IPlayerHotbarLoadoutStore, InMemoryPlayerHotbarLoadoutStore>();
services.AddSingleton<IPlayerSkillProgressionStore, InMemoryPlayerSkillProgressionStore>();
services.AddSingleton<IPlayerPerkStateStore, InMemoryPlayerPerkStateStore>();
services.AddSingleton<IPlayerAbilityCooldownStore, InMemoryPlayerAbilityCooldownStore>();
services.AddSingleton<ISkillDefinitionRegistry, RefineActivityDeniedSkillRegistry>();
});
}
}
/// <summary>Prototype trio ids; <c>refine</c> excludes <c>activity</c> so craft/refine grant path denies without throwing.</summary>
internal sealed class RefineActivityDeniedSkillRegistry : ISkillDefinitionRegistry
{
private static readonly SkillDefRow Salvage = new(
"salvage",
"gather",
"Salvage",
new[] { "activity", "mission_reward" });
private static readonly SkillDefRow RefineNoActivity = new(
"refine",
"process",
"Refine",
new[] { "mission_reward", "trainer" });
private static readonly SkillDefRow Intrusion = new(
"intrusion",
"tech",
"Intrusion",
new[] { "activity", "mission_reward", "book_or_item" });
private static readonly IReadOnlyList<SkillDefRow> 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<SkillDefRow> GetDefinitionsInIdOrder() => Ordered;
}