neon-sprawl/server/NeonSprawl.Server.Tests/Game/Contracts/ContractGeneratorPersistenc...

100 lines
4.4 KiB
C#

using NeonSprawl.Server.Game.Contracts;
using NeonSprawl.Server.Game.Encounters;
using NeonSprawl.Server.Game.Factions;
using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.Skills;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Tests.Game.PositionState;
namespace NeonSprawl.Server.Tests.Game.Contracts;
[Collection("Postgres integration")]
public sealed class ContractGeneratorPersistenceIntegrationTests(PostgresIntegrationHarness harness)
{
private PostgresWebApplicationFactory Factory => harness.Factory;
private const string PlayerId = "dev-local-1";
private const string TemplateId = "prototype_contract_clear_combat_pocket";
private const string EncounterId = "prototype_combat_pocket";
private const string SeedBucket = "2026-06-22";
[RequirePostgresFact]
public async Task TryIssue_ShouldPersistActiveInstanceAcrossNewFactory()
{
// Arrange
await ResetContractInstanceTableAsync();
var expectedInstanceId = ContractInstanceIds.MakeDeterministicInstanceId(
PlayerId,
TemplateId,
SeedBucket);
// Act
ContractIssueOperationResult issued;
using (var firstScope = Factory.Services.CreateScope())
{
var services = firstScope.ServiceProvider;
issued = ContractGeneratorOperations.TryIssue(
PlayerId,
TemplateId,
SeedBucket,
zoneDifficultyBand: null,
services.GetRequiredService<IContractTemplateRegistry>(),
services.GetRequiredService<IEncounterDefinitionRegistry>(),
services.GetRequiredService<IItemDefinitionRegistry>(),
services.GetRequiredService<ISkillDefinitionRegistry>(),
services.GetRequiredService<IFactionDefinitionRegistry>(),
services.GetRequiredService<IContractInstanceStore>(),
services.GetRequiredService<IFactionStandingStore>(),
services.GetRequiredService<TimeProvider>());
}
// Assert
Assert.True(issued.Success);
Assert.Equal(expectedInstanceId, issued.Snapshot!.ContractInstanceId);
Assert.Equal(EncounterId, issued.EncounterTemplateId);
await using var secondFactory = new PostgresWebApplicationFactory();
using var secondScope = secondFactory.Services.CreateScope();
var readStore = secondScope.ServiceProvider.GetRequiredService<IContractInstanceStore>();
Assert.True(readStore.TryGetActiveForPlayer(PlayerId, out var readBack));
Assert.Equal(expectedInstanceId, readBack.ContractInstanceId);
Assert.Equal(TemplateId, readBack.TemplateId);
Assert.Equal(ContractInstanceStatus.Active, readBack.Status);
Assert.Equal(SeedBucket, readBack.SeedBucket);
}
private async Task ResetContractInstanceTableAsync()
{
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 instanceDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V011__contract_instance.sql");
if (!File.Exists(positionDdlPath) || !File.Exists(instanceDdlPath))
{
throw new FileNotFoundException("Test DDL for contract generator persistence not found.");
}
var positionDdl = await File.ReadAllTextAsync(positionDdlPath);
var instanceDdl = await File.ReadAllTextAsync(instanceDdlPath);
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 applyInstance = new NpgsqlCommand(instanceDdl, conn);
await applyInstance.ExecuteNonQueryAsync();
}
}