proposal-system/api/tests/ProposalSystem.Tests/Helpers/SqliteDbContextFactory.cs
Adam Moussa a4e59040d6
test(api): concurrency, codec, 409 wire-shape, and audit-atomicity coverage
- ProposalConcurrencyTests (shared-connection SQLite, two competing
  writers): stale-token pre-check with embedded currentState, DB-level
  lost race reloading the winner's values, 422 token codes, version
  bump on success, bulk-update proposal-token guard, line-item create
  bumping the aggregate, audit rows never persisted on a lost race.
- GlobalExceptionHandlerTests: pin both SHOC 409 envelopes verbatim
  (guarded { message, currentState } incl. null state; fallback
  { status, message, code }) and assert shape exclusivity.
- RowVersionCodecTests: round-trip, SHOC-style token literal,
  malformed/wrong-length rejection.
- Existing suites threaded with live version tokens (Ver helper);
  audit assertions moved from LogAsync to Stage.

187 xUnit green (was 166).
2026-07-13 20:48:28 -04:00

71 lines
2.7 KiB
C#

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using ProposalSystem.Infrastructure.Data;
namespace ProposalSystem.Tests.Helpers;
/// <summary>
/// Creates SQLite-backed in-memory DbContext instances for tests that require relational
/// features (e.g., ExecuteSqlRawAsync, transactions). The SQLite connection is kept open
/// for the lifetime of the context; disposing the context closes the connection and
/// discards the in-memory database.
///
/// Registers stub Postgres functions (hashtext, pg_advisory_xact_lock) so that
/// production SQL using these functions does not throw in the test environment.
/// </summary>
public static class SqliteDbContextFactory
{
/// <summary>
/// Creates a new ProposalDbContext backed by a unique SQLite in-memory database.
/// The returned context owns the connection; dispose the context to clean up.
/// </summary>
public static ProposalDbContext Create()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
// Register Postgres-specific function stubs so ExecuteSqlRawAsync calls
// like "SELECT pg_advisory_xact_lock(hashtext('...'))" succeed in SQLite.
RegisterPostgresStubs(connection);
var options = new DbContextOptionsBuilder<ProposalDbContext>()
.UseSqlite(connection)
.Options;
var context = new ProposalDbContext(options);
context.Database.EnsureCreated();
return context;
}
/// <summary>
/// Opens a shared in-memory database and returns the connection plus a factory
/// for additional contexts over the SAME database — needed by optimistic-
/// concurrency tests that simulate two competing writers. Callers dispose the
/// connection after the contexts.
/// </summary>
public static (SqliteConnection Connection, Func<ProposalDbContext> ContextFactory) CreateShared()
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
RegisterPostgresStubs(connection);
var options = new DbContextOptionsBuilder<ProposalDbContext>()
.UseSqlite(connection)
.Options;
var first = new ProposalDbContext(options);
first.Database.EnsureCreated();
first.Dispose();
return (connection, () => new ProposalDbContext(options));
}
private static void RegisterPostgresStubs(SqliteConnection connection)
{
// hashtext(text) -> integer — returns a constant; only needed so SQL parses
connection.CreateFunction("hashtext", (string _) => 0);
// pg_advisory_xact_lock(bigint) -> void — no-op in tests
connection.CreateFunction("pg_advisory_xact_lock", (long _) => 0);
}
}