using NeonSprawl.Server.Game.Factions; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Tests.Game.PositionState; namespace NeonSprawl.Server.Tests.Game.Factions; [Collection("Postgres integration")] public sealed class ReputationDeltaPersistenceIntegrationTests(PostgresIntegrationHarness harness) { private PostgresWebApplicationFactory Factory => harness.Factory; private const string PlayerId = "dev-local-1"; private const string GridFactionId = "prototype_faction_grid_operators"; private static readonly DateTimeOffset RecordedAt = new(2026, 6, 15, 14, 0, 0, TimeSpan.Zero); [RequirePostgresFact] public async Task AppendThenGetAcrossNewFactory_ShouldPersistAuditRow() { // Arrange await ResetReputationDeltaTableAsync(); var row = new ReputationDeltaRow( "delta-persist-001", PlayerId, GridFactionId, 15, 15, ReputationDeltaSourceKinds.QuestCompletion, "prototype_quest_operator_chain", RecordedAt); // Act — write through first host using (var firstScope = Factory.Services.CreateScope()) { var store = firstScope.ServiceProvider.GetRequiredService(); Assert.True(store.TryAppend(row)); } IReadOnlyList readBack; await using var secondFactory = new PostgresWebApplicationFactory(); using var secondScope = secondFactory.Services.CreateScope(); var readStore = secondScope.ServiceProvider.GetRequiredService(); readBack = readStore.GetDeltasForPlayer(PlayerId); // Assert var persisted = Assert.Single(readBack); Assert.Equal(row.Id, persisted.Id); Assert.Equal(15, persisted.DeltaAmount); Assert.Equal(15, persisted.ResultingStanding); Assert.Equal(ReputationDeltaSourceKinds.QuestCompletion, persisted.SourceKind); } private async Task ResetReputationDeltaTableAsync() { 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>().Value; var positionDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V001__player_position.sql"); var auditDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V010__reputation_delta_audit.sql"); if (!File.Exists(positionDdlPath) || !File.Exists(auditDdlPath)) { throw new FileNotFoundException("Test DDL for reputation delta persistence not found."); } var positionDdl = await File.ReadAllTextAsync(positionDdlPath); var auditDdl = await File.ReadAllTextAsync(auditDdlPath); 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 applyAudit = new NpgsqlCommand(auditDdl, conn); await applyAudit.ExecuteNonQueryAsync(); } }