neon-sprawl/server/NeonSprawl.Server.Tests/Game/Factions/InMemoryFactionStandingStor...

208 lines
7.6 KiB
C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NeonSprawl.Server.Game.Factions;
using NeonSprawl.Server.Game.PositionState;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Factions;
public sealed class InMemoryFactionStandingStoreTests
{
private const string PlayerId = "dev-local-1";
private const string UnknownPlayerId = "unknown-player-xyz";
private const string GridFactionId = "prototype_faction_grid_operators";
private const string RustFactionId = "prototype_faction_rust_collective";
[Fact]
public void TryGetStanding_ShouldReturnZero_WhenRowMissing()
{
// Arrange
var store = CreateStore();
// Act
var outcome = store.TryGetStanding(PlayerId, GridFactionId);
// Assert
Assert.True(outcome.Success);
Assert.Null(outcome.ReasonCode);
Assert.Equal(0, outcome.Standing);
}
[Fact]
public void TryApplyStandingDelta_ShouldReturnFifteen_WhenFirstApplyFromZero()
{
// Arrange
var store = CreateStore();
// Act
var outcome = store.TryApplyStandingDelta(PlayerId, GridFactionId, 15);
// Assert
Assert.True(outcome.Success);
Assert.Equal(0, outcome.PreviousStanding);
Assert.Equal(15, outcome.NewStanding);
var readBack = store.TryGetStanding(PlayerId, GridFactionId);
Assert.True(readBack.Success);
Assert.Equal(15, readBack.Standing);
}
[Fact]
public void TryApplyStandingDelta_ShouldClampToMax_WhenDeltaExceedsBand()
{
// Arrange
var store = CreateStore();
Assert.True(store.TryApplyStandingDelta(PlayerId, GridFactionId, 95).Success);
// Act
var outcome = store.TryApplyStandingDelta(PlayerId, GridFactionId, 50);
// Assert
Assert.True(outcome.Success);
Assert.Equal(95, outcome.PreviousStanding);
Assert.Equal(100, outcome.NewStanding);
Assert.Equal(100, store.TryGetStanding(PlayerId, GridFactionId).Standing);
}
[Fact]
public void TryApplyStandingDelta_ShouldClampToMin_WhenDeltaBelowBand()
{
// Arrange
var store = CreateStore();
Assert.True(store.TryApplyStandingDelta(PlayerId, GridFactionId, -95).Success);
// Act
var outcome = store.TryApplyStandingDelta(PlayerId, GridFactionId, -50);
// Assert
Assert.True(outcome.Success);
Assert.Equal(-95, outcome.PreviousStanding);
Assert.Equal(-100, outcome.NewStanding);
Assert.Equal(-100, store.TryGetStanding(PlayerId, GridFactionId).Standing);
}
[Fact]
public void TryGetStanding_ShouldClampRead_WhenRawStoredValueExceedsMax()
{
// Arrange
var store = CreateStore();
store.SeedRawStandingForTests(PlayerId, GridFactionId, 999);
// Act
var outcome = store.TryGetStanding(PlayerId, GridFactionId);
// Assert
Assert.True(outcome.Success);
Assert.Null(outcome.ReasonCode);
Assert.Equal(100, outcome.Standing);
}
[Fact]
public void TryGetStanding_ShouldReturnUnknownFaction_WhenFactionIdEmpty()
{
// Arrange
var store = CreateStore();
// Act
var read = store.TryGetStanding(PlayerId, " ");
var write = store.TryApplyStandingDelta(PlayerId, "", 5);
// Assert
Assert.False(read.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, read.ReasonCode);
Assert.False(write.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, write.ReasonCode);
}
[Fact]
public void TryGetStanding_ShouldDenyUnknownFaction_WithStructuredReason()
{
// Arrange
var store = CreateStore();
// Act
var read = store.TryGetStanding(PlayerId, "not_a_real_faction");
var write = store.TryApplyStandingDelta(PlayerId, "not_a_real_faction", 5);
// Assert
Assert.False(read.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, read.ReasonCode);
Assert.Equal(0, read.Standing);
Assert.False(write.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, write.ReasonCode);
Assert.Equal(0, store.TryGetStanding(PlayerId, GridFactionId).Standing);
}
[Fact]
public void TryGetStanding_ShouldPreferUnknownFaction_WhenPlayerNotWritableAndFactionUnknown()
{
// Arrange
var store = CreateStore();
// Act
var read = store.TryGetStanding(UnknownPlayerId, "not_a_real_faction");
var write = store.TryApplyStandingDelta(UnknownPlayerId, "not_a_real_faction", 5);
// Assert
Assert.False(read.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, read.ReasonCode);
Assert.False(write.Success);
Assert.Equal(FactionStandingReasonCodes.UnknownFaction, write.ReasonCode);
}
[Fact]
public void TryApplyStandingDelta_ShouldDenyNonWritablePlayer()
{
// Arrange
var store = CreateStore();
// Act
var outcome = store.TryApplyStandingDelta(UnknownPlayerId, GridFactionId, 10);
// Assert
Assert.False(outcome.Success);
Assert.Equal(FactionStandingReasonCodes.PlayerNotWritable, outcome.ReasonCode);
}
[Fact]
public void TryGetStanding_ShouldDenyNonWritablePlayer()
{
// Arrange
var store = CreateStore();
// Act
var outcome = store.TryGetStanding(UnknownPlayerId, GridFactionId);
// Assert
Assert.False(outcome.Success);
Assert.Equal(FactionStandingReasonCodes.PlayerNotWritable, outcome.ReasonCode);
}
[Fact]
public void TryApplyStandingDelta_ShouldTrackSeparateFactionsPerPlayer()
{
// Arrange
var store = CreateStore();
// Act
Assert.True(store.TryApplyStandingDelta(PlayerId, GridFactionId, 15).Success);
Assert.True(store.TryApplyStandingDelta(PlayerId, RustFactionId, 10).Success);
// Assert
Assert.Equal(15, store.TryGetStanding(PlayerId, GridFactionId).Standing);
Assert.Equal(10, store.TryGetStanding(PlayerId, RustFactionId).Standing);
}
[Fact]
public async Task Host_ShouldResolveFactionStandingStoresFromDi_WhenStartupSucceeds()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
using var client = factory.CreateClient();
_ = await client.GetAsync("/health");
// Act
var standingStore = factory.Services.GetRequiredService<IFactionStandingStore>();
var auditStore = factory.Services.GetRequiredService<IReputationDeltaStore>();
var read = standingStore.TryGetStanding(PlayerId, GridFactionId);
// Assert
Assert.NotNull(auditStore);
Assert.True(read.Success);
Assert.Equal(0, read.Standing);
}
private static InMemoryFactionStandingStore CreateStore()
{
var registry = CreatePrototypeRegistry();
var options = Options.Create(new GamePositionOptions { DevPlayerId = PlayerId });
return new InMemoryFactionStandingStore(options, registry);
}
private static FactionDefinitionRegistry CreatePrototypeRegistry()
{
var byId = new Dictionary<string, FactionDefRow>(StringComparer.Ordinal)
{
[GridFactionId] = PrototypeE7M3FactionCatalogRules.ExpectedFactionFreeze[GridFactionId],
[RustFactionId] = PrototypeE7M3FactionCatalogRules.ExpectedFactionFreeze[RustFactionId],
};
var catalog = new FactionDefinitionCatalog("/tmp/catalog", byId, catalogJsonFileCount: 1);
return new FactionDefinitionRegistry(catalog);
}
}