neon-sprawl/server/NeonSprawl.Server.Tests/Game/Encounters/EncounterDefinitionRegistry...

229 lines
8.9 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.Encounters;
using NeonSprawl.Server.Tests;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Encounters;
public class EncounterDefinitionRegistryTests
{
private const string PrototypeCombatPocket = "prototype_combat_pocket";
private static EncounterDefinitionRegistry CreateRegistryFromRows(IReadOnlyDictionary<string, EncounterDefRow> byId)
{
var catalog = new EncounterDefinitionCatalog("/tmp/encounters", byId, catalogJsonFileCount: 1);
return new EncounterDefinitionRegistry(catalog);
}
private static EncounterDefRow CreatePrototypeCombatPocketRow() =>
new(
PrototypeCombatPocket,
"Prototype Combat Pocket",
"defeat_all_targets",
["prototype_npc_melee", "prototype_npc_ranged", "prototype_npc_elite"],
"prototype_combat_pocket_clear");
[Fact]
public void TryGetDefinition_ShouldReturnTrueAndExpectedMetadata_WhenIdExists()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryGetDefinition(PrototypeCombatPocket, out var def);
// Assert
Assert.True(found);
Assert.NotNull(def);
Assert.Equal("Prototype Combat Pocket", def!.DisplayName);
Assert.Equal("defeat_all_targets", def.CompletionCriteriaKind);
Assert.Equal("prototype_combat_pocket_clear", def.RewardTableId);
Assert.Equal(3, def.RequiredNpcInstanceIds.Count);
Assert.Contains("prototype_npc_melee", def.RequiredNpcInstanceIds);
}
[Fact]
public void TryGetDefinition_ShouldReturnFalse_WhenEncounterIdIsNull()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryGetDefinition(null, out var def);
// Assert
Assert.False(found);
Assert.Null(def);
}
[Fact]
public void TryGetDefinition_ShouldReturnFalse_WhenIdUnknown()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryGetDefinition("not_a_real_encounter", out var def);
// Assert
Assert.False(found);
Assert.Null(def);
}
[Fact]
public void TryNormalizeKnown_ShouldReturnTrueAndLowercaseId_WhenInputHasWhitespaceAndMixedCase()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryNormalizeKnown(" Prototype_Combat_Pocket ", out var normalized);
// Assert
Assert.True(found);
Assert.Equal(PrototypeCombatPocket, normalized);
}
[Fact]
public void TryNormalizeKnown_ShouldReturnFalse_WhenInputIsNull()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryNormalizeKnown(null, out var normalized);
// Assert
Assert.False(found);
Assert.Equal(string.Empty, normalized);
}
[Fact]
public void TryNormalizeKnown_ShouldReturnFalse_WhenInputIsWhitespaceOnly()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryNormalizeKnown(" ", out var normalized);
// Assert
Assert.False(found);
Assert.Equal(string.Empty, normalized);
}
[Fact]
public void TryNormalizeKnown_ShouldReturnFalse_WhenIdNotInCatalog()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
};
var registry = CreateRegistryFromRows(rows);
// Act
var found = registry.TryNormalizeKnown("prototype_unknown", out var normalized);
// Assert
Assert.False(found);
Assert.Equal("prototype_unknown", normalized);
}
[Fact]
public void GetDefinitionsInIdOrder_ShouldListAllRowsOrderedById_WhenMultipleEncounters()
{
// Arrange
var rows = new Dictionary<string, EncounterDefRow>(StringComparer.Ordinal)
{
["z_encounter"] = new EncounterDefRow(
"z_encounter",
"Z",
"defeat_all_targets",
["prototype_npc_melee"],
"prototype_combat_pocket_clear"),
[PrototypeCombatPocket] = CreatePrototypeCombatPocketRow(),
["a_encounter"] = new EncounterDefRow(
"a_encounter",
"A",
"defeat_all_targets",
["prototype_npc_melee"],
"prototype_combat_pocket_clear"),
};
var registry = CreateRegistryFromRows(rows);
// Act
var list = registry.GetDefinitionsInIdOrder();
// Assert
Assert.Equal(3, list.Count);
Assert.Equal("a_encounter", list[0].Id);
Assert.Equal(PrototypeCombatPocket, list[1].Id);
Assert.Equal("z_encounter", list[2].Id);
}
[Fact]
public async Task Host_ShouldResolveRegistriesFromDi_WhenStartupSucceeds()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
using var client = factory.CreateClient();
_ = await client.GetAsync("/health");
// Act
var encounterRegistry = factory.Services.GetRequiredService<IEncounterDefinitionRegistry>();
var rewardRegistry = factory.Services.GetRequiredService<IRewardTableDefinitionRegistry>();
var encounterFound = encounterRegistry.TryGetDefinition(PrototypeCombatPocket, out var encounter);
var unknownEncounter = encounterRegistry.TryGetDefinition("not_a_real_encounter", out var missingEncounter);
var normalizedEncounter = encounterRegistry.TryNormalizeKnown(" Prototype_Combat_Pocket ", out var encounterId);
var rewardFound = rewardRegistry.TryGetDefinition("prototype_combat_pocket_clear", out var rewardTable);
var unknownReward = rewardRegistry.TryNormalizeKnown("prototype_unknown", out var unknownRewardId);
var encounterList = encounterRegistry.GetDefinitionsInIdOrder();
// Assert
Assert.True(encounterFound);
Assert.NotNull(encounter);
Assert.Equal("prototype_combat_pocket_clear", encounter!.RewardTableId);
Assert.False(unknownEncounter);
Assert.Null(missingEncounter);
Assert.True(normalizedEncounter);
Assert.Equal(PrototypeCombatPocket, encounterId);
Assert.True(rewardFound);
Assert.NotNull(rewardTable);
Assert.Equal(10, rewardTable!.FixedGrants.First(g => g.ItemId == "scrap_metal_bulk").Quantity);
Assert.False(unknownReward);
Assert.Equal("prototype_unknown", unknownRewardId);
Assert.Single(encounterList);
Assert.Equal(PrototypeCombatPocket, encounterList[0].Id);
}
[Fact]
public async Task Host_ShouldResolveEncounterProgressStoresFromDi_WhenStartupSucceeds()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
using var client = factory.CreateClient();
_ = await client.GetAsync("/health");
var progressStore = factory.Services.GetRequiredService<IEncounterProgressStore>();
var completionStore = factory.Services.GetRequiredService<IEncounterCompletionStore>();
var completeEventStore = factory.Services.GetRequiredService<IEncounterCompleteEventStore>();
// Act
var activated = progressStore.TryActivate("dev-local-1", PrototypeCombatPocket);
var completed = completionStore.TryMarkCompleted(
"dev-local-1",
PrototypeCombatPocket,
new DateTimeOffset(2026, 5, 31, 12, 0, 0, TimeSpan.Zero));
// Assert
Assert.True(activated);
Assert.True(completed);
Assert.True(completionStore.IsCompleted("dev-local-1", PrototypeCombatPocket));
Assert.IsType<InMemoryEncounterCompleteEventStore>(completeEventStore);
}
}