using System.Net.Http.Json; using NeonSprawl.Server.Game.Skills; using NeonSprawl.Server.Tests.Game.PositionState; using Npgsql; using Xunit; namespace NeonSprawl.Server.Tests.Game.Skills; [Collection("Postgres integration")] public sealed class SkillProgressionGrantPersistenceIntegrationTests(PostgresIntegrationHarness harness) { private PostgresWebApplicationFactory Factory => harness.Factory; [RequirePostgresFact] public async Task PostGrantThenGetAcrossNewFactory_ShouldPersistSkillXp() { // Arrange await ResetProgressionTableAsync(); var grant = new SkillProgressionGrantRequest { SchemaVersion = SkillProgressionGrantRequest.CurrentSchemaVersion, SkillId = "salvage", Amount = 50, SourceKind = "activity", }; HttpResponseMessage postResponse; // Act using (var firstClient = Factory.CreateClient()) { postResponse = await firstClient.PostAsJsonAsync( "/game/players/dev-local-1/skill-progression", grant); } await using var secondFactory = new PostgresWebApplicationFactory(); using var secondClient = secondFactory.CreateClient(); var snapshot = await secondClient.GetFromJsonAsync( "/game/players/dev-local-1/skill-progression"); // Assert Assert.True(postResponse.IsSuccessStatusCode); Assert.NotNull(snapshot); var salvage = Assert.Single(snapshot!.Skills!, static s => s.Id == "salvage"); Assert.Equal(50, salvage.Xp); Assert.Equal(1, salvage.Level); } private static async Task ResetProgressionTableAsync() { var cs = Environment.GetEnvironmentVariable("ConnectionStrings__NeonSprawl"); if (string.IsNullOrWhiteSpace(cs)) { throw new InvalidOperationException("ConnectionStrings__NeonSprawl is not set."); } var ddlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V003__player_skill_progression.sql"); if (!File.Exists(ddlPath)) { throw new FileNotFoundException($"Test DDL not found at '{ddlPath}'.", ddlPath); } var ddl = await File.ReadAllTextAsync(ddlPath); await using var conn = new NpgsqlConnection(cs); await conn.OpenAsync(); await using (var apply = new NpgsqlCommand(ddl, conn)) { await apply.ExecuteNonQueryAsync(); } await using (var truncate = new NpgsqlCommand("TRUNCATE player_skill_progression;", conn)) { await truncate.ExecuteNonQueryAsync(); } } }