using NeonSprawl.Server.Game.Mastery; using NeonSprawl.Server.Game.PositionState; using NeonSprawl.Server.Tests.Game.PositionState; namespace NeonSprawl.Server.Tests.Game.Mastery; [Collection("Postgres integration")] public sealed class PerkStatePersistenceIntegrationTests(PostgresIntegrationHarness harness) { private PostgresWebApplicationFactory Factory => harness.Factory; [RequirePostgresFact] public async Task BranchPickAndUnlock_ShouldPersistAcrossNewFactory() { // Arrange await ResetPerkTablesAsync(); const string playerId = "dev-local-1"; using var scope = Factory.Services.CreateScope(); var xpStore = scope.ServiceProvider.GetRequiredService(); var engine = scope.ServiceProvider.GetRequiredService(); Assert.True(xpStore.TryApplyXpDelta(playerId, "salvage", 450, out _, out _)); Assert.True(engine.TrySelectBranch(playerId, "salvage", 1, "scrap_efficiency").Success); _ = engine.ReevaluateAfterLevelUp(playerId, "salvage", newLevel: 4); // Act PerkStateSnapshot snapshot; await using var secondFactory = new PostgresWebApplicationFactory(); using var secondScope = secondFactory.Services.CreateScope(); var perkStore = secondScope.ServiceProvider.GetRequiredService(); snapshot = perkStore.GetSnapshot(playerId); // Assert Assert.Equal("scrap_efficiency", snapshot.BranchPicksBySkillId["salvage"][1]); Assert.Contains("salvage_scrap_efficiency_1", snapshot.UnlockedPerkIds); } private async Task ResetPerkTablesAsync() { 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 perkDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V004__player_perk_state.sql"); if (!File.Exists(positionDdlPath) || !File.Exists(perkDdlPath)) { throw new FileNotFoundException("Test DDL for perk persistence not found."); } var positionDdl = await File.ReadAllTextAsync(positionDdlPath); var perkDdl = await File.ReadAllTextAsync(perkDdlPath); 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 applyPerk = new NpgsqlCommand(perkDdl, conn); await applyPerk.ExecuteNonQueryAsync(); } }