48a7f5a825
Five-project Clean Architecture solution (Core/Infrastructure/App + Core.Tests/ Integration.Tests) on .NET 10 with central package management, snake_case EF mapping, and shared build/style config. - Full domain entity set + EF DbContext for the SDD §5.3 schema (singular table names). - InitialSchema migration (relational) + TimescaleHypertables migration (raw SQL: create_hypertable + compression on reading, hypertable on consumption). - App wiring: Serilog (actually wired, unlike MQTTower), DbContext, migrate-on-startup, /healthz. Serves plain HTTP behind a reverse proxy (no HTTPS redirect). - deploy/Dockerfile (2-stage, ICU-capable) + docker-compose (app + timescaledb). - Integration.Tests: shared TimescaleFixture (Testcontainers) — migrations, hypertables, compression policy, and /healthz all verified green (4/4). Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
46 lines
1.7 KiB
C#
46 lines
1.7 KiB
C#
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Testcontainers.PostgreSql;
|
|
|
|
namespace MeterVault.Integration.Tests;
|
|
|
|
/// <summary>
|
|
/// Spins up exactly one TimescaleDB container per test run (shared via the "Timescale"
|
|
/// collection) and applies migrations once. Tests reuse it and isolate themselves with Respawn.
|
|
/// The image tag is pinned: the compression DDL (add_compression_policy) was renamed toward
|
|
/// add_columnstore_policy in newer Timescale, so a floating tag would risk breaking migrations.
|
|
/// </summary>
|
|
public sealed class TimescaleFixture : IAsyncLifetime
|
|
{
|
|
private readonly PostgreSqlContainer _db = new PostgreSqlBuilder("timescale/timescaledb:2.17.2-pg16")
|
|
.WithDatabase("metervault")
|
|
.WithUsername("metervault")
|
|
.WithPassword("metervault")
|
|
.Build();
|
|
|
|
public string ConnectionString => _db.GetConnectionString();
|
|
|
|
public MeterVaultDbContext CreateContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<MeterVaultDbContext>()
|
|
.UseNpgsql(ConnectionString, npgsql =>
|
|
npgsql.MigrationsAssembly(typeof(MeterVaultDbContext).Assembly.FullName))
|
|
.UseSnakeCaseNamingConvention()
|
|
.Options;
|
|
return new MeterVaultDbContext(options);
|
|
}
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
await _db.StartAsync();
|
|
await using var ctx = CreateContext();
|
|
await ctx.Database.MigrateAsync();
|
|
}
|
|
|
|
public async Task DisposeAsync() => await _db.DisposeAsync();
|
|
}
|
|
|
|
/// <summary>Binds the shared <see cref="TimescaleFixture"/> to all tests in the "Timescale" collection.</summary>
|
|
[CollectionDefinition("Timescale")]
|
|
public sealed class TimescaleCollection : ICollectionFixture<TimescaleFixture>;
|