120 lines
4.9 KiB
C#
120 lines
4.9 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.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>salvage</c> does not allow <c>mission_reward</c> — NEO-43 defensive tests without mutating disk catalog.
|
|
/// </summary>
|
|
public sealed class MissionRewardDeniedRegistryWebApplicationFactory : 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.");
|
|
builder.UseSetting("Content:SkillsDirectory", skillsDir);
|
|
builder.UseSetting("Content:MasteryDirectory", masteryDir);
|
|
|
|
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, MissionRewardDeniedSkillRegistry>();
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>Prototype trio ids; <c>salvage</c> excludes <c>mission_reward</c> so mission grant path denies without throwing.</summary>
|
|
internal sealed class MissionRewardDeniedSkillRegistry : ISkillDefinitionRegistry
|
|
{
|
|
private static readonly SkillDefRow SalvageNoMissionReward = new(
|
|
"salvage",
|
|
"gather",
|
|
"Salvage",
|
|
new[] { "activity" });
|
|
|
|
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<SkillDefRow> Ordered =
|
|
[
|
|
Intrusion,
|
|
Refine,
|
|
SalvageNoMissionReward,
|
|
];
|
|
|
|
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;
|
|
}
|