using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Gathering; using NeonSprawl.Server.Tests.Game.PositionState; using Npgsql; using Xunit; namespace NeonSprawl.Server.Tests.Game.Gathering; [Collection("Postgres integration")] public sealed class ResourceNodeInstancePersistenceIntegrationTests(PostgresIntegrationHarness harness) { private PostgresWebApplicationFactory Factory => harness.Factory; private const string AlphaId = "prototype_resource_node_alpha"; [RequirePostgresFact] public async Task TryCommitSuccessfulGatherDecrementAcrossNewFactory_ShouldPersistRemainingGathers() { // Arrange await ResetInstanceTableAsync(); ResourceNodeInstanceMutationOutcome firstOutcome; // Act — write through first host using (var firstScope = Factory.Services.CreateScope()) { var registry = firstScope.ServiceProvider.GetRequiredService(); var store = firstScope.ServiceProvider.GetRequiredService(); firstOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store); } // Act — read back through a fresh host await using var secondFactory = new PostgresWebApplicationFactory(); using var secondScope = secondFactory.Services.CreateScope(); var readStore = secondScope.ServiceProvider.GetRequiredService(); var readBack = readStore.TryGetRemainingGathers(AlphaId, out var snapshot); // Assert Assert.Equal(ResourceNodeInstanceMutationKind.Applied, firstOutcome.Kind); Assert.Equal(9, firstOutcome.Snapshot!.Value.RemainingGathers); Assert.True(readBack); Assert.Equal(9, snapshot.RemainingGathers); } [RequirePostgresFact] public async Task TryCommitSuccessfulGatherDecrement_WhenDepleted_ShouldPersistZeroRow() { // Arrange await ResetInstanceTableAsync(); using (var seedScope = Factory.Services.CreateScope()) { var seedRegistry = seedScope.ServiceProvider.GetRequiredService(); var seedStore = seedScope.ServiceProvider.GetRequiredService(); for (var i = 0; i < 10; i++) { _ = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, seedRegistry, seedStore); } } // Act ResourceNodeInstanceMutationOutcome denyOutcome; ResourceNodeInstanceSnapshot persisted; await using var secondFactory = new PostgresWebApplicationFactory(); using var actScope = secondFactory.Services.CreateScope(); var actRegistry = actScope.ServiceProvider.GetRequiredService(); var actStore = actScope.ServiceProvider.GetRequiredService(); denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, actRegistry, actStore); actStore.TryGetRemainingGathers(AlphaId, out persisted); // Assert Assert.Equal(ResourceNodeInstanceMutationKind.Denied, denyOutcome.Kind); Assert.Equal(ResourceNodeInstanceReasonCodes.NodeDepleted, denyOutcome.ReasonCode); Assert.Equal(0, persisted.RemainingGathers); } private async Task ResetInstanceTableAsync() { var cs = Environment.GetEnvironmentVariable("ConnectionStrings__NeonSprawl"); if (string.IsNullOrWhiteSpace(cs)) { throw new InvalidOperationException("ConnectionStrings__NeonSprawl is not set."); } _ = Factory.Services; var ddlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V006__resource_node_instance.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 resource_node_instance;", conn); await truncate.ExecuteNonQueryAsync(); } }