Add integration tests for migration categories and execution
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
- Implemented MigrationCategoryTests to validate migration categorization for startup, release, seed, and data migrations. - Added tests for edge cases, including null, empty, and whitespace migration names. - Created StartupMigrationHostTests to verify the behavior of the migration host with real PostgreSQL instances using Testcontainers. - Included tests for migration execution, schema creation, and handling of pending release migrations. - Added SQL migration files for testing: creating a test table, adding a column, a release migration, and seeding data.
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace StellaOps.Infrastructure.Postgres.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for running database migrations.
|
||||
/// </summary>
|
||||
public interface IMigrationRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the schema name for this migration runner.
|
||||
/// </summary>
|
||||
string SchemaName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the module name for this migration runner.
|
||||
/// </summary>
|
||||
string ModuleName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs pending migrations from the specified path.
|
||||
/// </summary>
|
||||
/// <param name="migrationsPath">Path to directory containing SQL migration files.</param>
|
||||
/// <param name="options">Migration execution options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Result of migration execution.</returns>
|
||||
Task<MigrationResult> RunAsync(
|
||||
string migrationsPath,
|
||||
MigrationRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Runs pending migrations from embedded resources in an assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly containing embedded migration resources.</param>
|
||||
/// <param name="resourcePrefix">Optional prefix to filter resources.</param>
|
||||
/// <param name="options">Migration execution options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Result of migration execution.</returns>
|
||||
Task<MigrationResult> RunFromAssemblyAsync(
|
||||
Assembly assembly,
|
||||
string? resourcePrefix = null,
|
||||
MigrationRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current migration version (latest applied migration).
|
||||
/// </summary>
|
||||
Task<string?> GetCurrentVersionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all applied migrations.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<MigrationInfo>> GetAppliedMigrationInfoAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Validates checksums of applied migrations against source files.
|
||||
/// </summary>
|
||||
/// <param name="assembly">Assembly containing embedded migration resources.</param>
|
||||
/// <param name="resourcePrefix">Optional prefix to filter resources.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>List of checksum validation errors, empty if all valid.</returns>
|
||||
Task<IReadOnlyList<string>> ValidateChecksumsAsync(
|
||||
Assembly assembly,
|
||||
string? resourcePrefix = null,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for migration execution.
|
||||
/// </summary>
|
||||
public sealed class MigrationRunOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Filter migrations by category. If null, all categories are included.
|
||||
/// </summary>
|
||||
public MigrationCategory? CategoryFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, only show what would be executed without applying.
|
||||
/// </summary>
|
||||
public bool DryRun { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in seconds for individual migration execution. Default: 300.
|
||||
/// </summary>
|
||||
public int TimeoutSeconds { get; set; } = 300;
|
||||
|
||||
/// <summary>
|
||||
/// If true, validate checksums before applying new migrations.
|
||||
/// </summary>
|
||||
public bool ValidateChecksums { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, fail if checksum validation errors are found.
|
||||
/// </summary>
|
||||
public bool FailOnChecksumMismatch { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a migration execution.
|
||||
/// </summary>
|
||||
public sealed class MigrationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the migration run was successful.
|
||||
/// </summary>
|
||||
public bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of migrations applied.
|
||||
/// </summary>
|
||||
public int AppliedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of migrations skipped (already applied).
|
||||
/// </summary>
|
||||
public int SkippedCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of migrations filtered out by category.
|
||||
/// </summary>
|
||||
public int FilteredCount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total duration in milliseconds.
|
||||
/// </summary>
|
||||
public long DurationMs { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Details of applied migrations.
|
||||
/// </summary>
|
||||
public IReadOnlyList<AppliedMigrationDetail> AppliedMigrations { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Checksum validation errors, if any.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ChecksumErrors { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Error message if migration failed.
|
||||
/// </summary>
|
||||
public string? ErrorMessage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful result.
|
||||
/// </summary>
|
||||
public static MigrationResult Successful(
|
||||
int appliedCount,
|
||||
int skippedCount,
|
||||
int filteredCount,
|
||||
long durationMs,
|
||||
IReadOnlyList<AppliedMigrationDetail> appliedMigrations) => new()
|
||||
{
|
||||
Success = true,
|
||||
AppliedCount = appliedCount,
|
||||
SkippedCount = skippedCount,
|
||||
FilteredCount = filteredCount,
|
||||
DurationMs = durationMs,
|
||||
AppliedMigrations = appliedMigrations
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failed result.
|
||||
/// </summary>
|
||||
public static MigrationResult Failed(string errorMessage, IReadOnlyList<string>? checksumErrors = null) => new()
|
||||
{
|
||||
Success = false,
|
||||
ErrorMessage = errorMessage,
|
||||
ChecksumErrors = checksumErrors ?? []
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details of an applied migration.
|
||||
/// </summary>
|
||||
public sealed record AppliedMigrationDetail(
|
||||
string Name,
|
||||
MigrationCategory Category,
|
||||
long DurationMs,
|
||||
bool WasDryRun);
|
||||
Reference in New Issue
Block a user