114 lines
4.6 KiB
C#
114 lines
4.6 KiB
C#
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using NeonSprawl.Server.Game.Factions;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
using NeonSprawl.Server.Tests.Game.PositionState;
|
|
using Npgsql;
|
|
using Xunit;
|
|
|
|
namespace NeonSprawl.Server.Tests.Game.Factions;
|
|
|
|
[Collection("Postgres integration")]
|
|
public sealed class FactionStandingPersistenceIntegrationTests(PostgresIntegrationHarness harness)
|
|
{
|
|
private PostgresWebApplicationFactory Factory => harness.Factory;
|
|
|
|
private const string PlayerId = "dev-local-1";
|
|
private const string GridFactionId = "prototype_faction_grid_operators";
|
|
|
|
[RequirePostgresFact]
|
|
public async Task ApplyDeltaThenGetAcrossNewFactory_ShouldPersistStanding()
|
|
{
|
|
// Arrange
|
|
await ResetFactionStandingTableAsync();
|
|
|
|
// Act — write through first host
|
|
FactionStandingMutationOutcome writeOutcome;
|
|
using (var firstScope = Factory.Services.CreateScope())
|
|
{
|
|
var store = firstScope.ServiceProvider.GetRequiredService<IFactionStandingStore>();
|
|
writeOutcome = store.TryApplyStandingDelta(PlayerId, GridFactionId, 15);
|
|
}
|
|
|
|
FactionStandingReadOutcome readBack;
|
|
await using var secondFactory = new PostgresWebApplicationFactory();
|
|
using var secondScope = secondFactory.Services.CreateScope();
|
|
var readStore = secondScope.ServiceProvider.GetRequiredService<IFactionStandingStore>();
|
|
readBack = readStore.TryGetStanding(PlayerId, GridFactionId);
|
|
|
|
// Assert
|
|
Assert.True(writeOutcome.Success);
|
|
Assert.Equal(15, writeOutcome.NewStanding);
|
|
Assert.True(readBack.Success);
|
|
Assert.Equal(15, readBack.Standing);
|
|
}
|
|
|
|
[RequirePostgresFact]
|
|
public async Task TryGetStanding_ShouldClampRead_WhenStoredValueExceedsMax()
|
|
{
|
|
// Arrange
|
|
await ResetFactionStandingTableAsync();
|
|
var cs = Environment.GetEnvironmentVariable("ConnectionStrings__NeonSprawl")
|
|
?? throw new InvalidOperationException("ConnectionStrings__NeonSprawl is not set.");
|
|
await using (var conn = new NpgsqlConnection(cs))
|
|
{
|
|
await conn.OpenAsync();
|
|
await using var insert = new NpgsqlCommand(
|
|
"""
|
|
INSERT INTO player_faction_standing (player_id, faction_id, standing, updated_at)
|
|
VALUES (@pid, @fid, @standing, now());
|
|
""",
|
|
conn);
|
|
insert.Parameters.AddWithValue("pid", PlayerId);
|
|
insert.Parameters.AddWithValue("fid", GridFactionId);
|
|
insert.Parameters.AddWithValue("standing", 999);
|
|
await insert.ExecuteNonQueryAsync();
|
|
}
|
|
|
|
// Act
|
|
FactionStandingReadOutcome readBack;
|
|
using var scope = Factory.Services.CreateScope();
|
|
var store = scope.ServiceProvider.GetRequiredService<IFactionStandingStore>();
|
|
readBack = store.TryGetStanding(PlayerId, GridFactionId);
|
|
|
|
|
|
// Assert
|
|
Assert.True(readBack.Success);
|
|
Assert.Equal(100, readBack.Standing);
|
|
}
|
|
|
|
private async Task ResetFactionStandingTableAsync()
|
|
{
|
|
var cs = Environment.GetEnvironmentVariable("ConnectionStrings__NeonSprawl");
|
|
if (string.IsNullOrWhiteSpace(cs))
|
|
{
|
|
throw new InvalidOperationException("ConnectionStrings__NeonSprawl is not set.");
|
|
}
|
|
|
|
_ = Factory.Services;
|
|
var options = Factory.Services.GetRequiredService<IOptions<GamePositionOptions>>().Value;
|
|
|
|
var positionDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V001__player_position.sql");
|
|
var standingDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V009__player_faction_standing.sql");
|
|
if (!File.Exists(positionDdlPath) || !File.Exists(standingDdlPath))
|
|
{
|
|
throw new FileNotFoundException("Test DDL for faction standing persistence not found.");
|
|
}
|
|
|
|
var positionDdl = await File.ReadAllTextAsync(positionDdlPath);
|
|
var standingDdl = await File.ReadAllTextAsync(standingDdlPath);
|
|
await using var conn = new NpgsqlConnection(cs);
|
|
await conn.OpenAsync();
|
|
await using var applyPosition = new NpgsqlCommand(positionDdl, conn);
|
|
await applyPosition.ExecuteNonQueryAsync();
|
|
|
|
await using var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn);
|
|
await truncate.ExecuteNonQueryAsync();
|
|
|
|
PostgresPositionBootstrap.SeedDevPlayer(conn, options);
|
|
|
|
await using var applyStanding = new NpgsqlCommand(standingDdl, conn);
|
|
await applyStanding.ExecuteNonQueryAsync();
|
|
}
|
|
}
|