104 lines
3.9 KiB
C#
104 lines
3.9 KiB
C#
using System.Net.Http.Json;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using NeonSprawl.Server.Game.PositionState;
|
|
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 — write grant through first host
|
|
using (var firstClient = Factory.CreateClient())
|
|
{
|
|
postResponse = await firstClient.PostAsJsonAsync(
|
|
"/game/players/dev-local-1/skill-progression",
|
|
grant);
|
|
}
|
|
|
|
// Act — read back through a fresh host (forces DB round-trip / new DI scope)
|
|
await using var secondFactory = new PostgresWebApplicationFactory();
|
|
using var secondClient = secondFactory.CreateClient();
|
|
var snapshot =
|
|
await secondClient.GetFromJsonAsync<SkillProgressionSnapshotResponse>(
|
|
"/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 async Task ResetProgressionTableAsync()
|
|
{
|
|
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 progressionDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V003__player_skill_progression.sql");
|
|
if (!File.Exists(positionDdlPath))
|
|
{
|
|
throw new FileNotFoundException($"Test DDL not found at '{positionDdlPath}'.", positionDdlPath);
|
|
}
|
|
|
|
if (!File.Exists(progressionDdlPath))
|
|
{
|
|
throw new FileNotFoundException($"Test DDL not found at '{progressionDdlPath}'.", progressionDdlPath);
|
|
}
|
|
|
|
var positionDdl = await File.ReadAllTextAsync(positionDdlPath);
|
|
var progressionDdl = await File.ReadAllTextAsync(progressionDdlPath);
|
|
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 applyProgression = new NpgsqlCommand(progressionDdl, conn))
|
|
{
|
|
await applyProgression.ExecuteNonQueryAsync();
|
|
}
|
|
|
|
await using (var truncateProgression = new NpgsqlCommand("TRUNCATE player_skill_progression;", conn))
|
|
{
|
|
await truncateProgression.ExecuteNonQueryAsync();
|
|
}
|
|
}
|
|
}
|