Add SiteReplicationActor (runs on every site node) to replicate deployed configs and store-and-forward buffer operations to the standby peer via cluster member discovery and fire-and-forget Tell. Wire ReplicationService handler and pass replication actor to DeploymentManagerActor singleton. Fix 5 pre-existing ConfigurationDatabase test failures: RowVersion NOT NULL on SQLite, stale migration name assertion, and seed data count mismatch.
88 lines
3.2 KiB
C#
88 lines
3.2 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
using ScadaLink.Commons.Entities.Deployment;
|
|
using ScadaLink.ConfigurationDatabase;
|
|
|
|
namespace ScadaLink.ConfigurationDatabase.Tests;
|
|
|
|
/// <summary>
|
|
/// Test DbContext that adapts SQL Server-specific features for SQLite:
|
|
/// - Maps DateTimeOffset to sortable ISO 8601 strings (SQLite has no native DateTimeOffset ORDER BY)
|
|
/// - Replaces SQL Server RowVersion with a nullable byte[] column (SQLite can't auto-generate rowversion)
|
|
/// </summary>
|
|
public class SqliteTestDbContext : ScadaLinkDbContext
|
|
{
|
|
public SqliteTestDbContext(DbContextOptions<ScadaLinkDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
// SQLite cannot auto-generate SQL Server rowversion values.
|
|
// Replace with a nullable byte[] column so inserts don't fail with NOT NULL constraint.
|
|
modelBuilder.Entity<DeploymentRecord>(builder =>
|
|
{
|
|
builder.Property(d => d.RowVersion)
|
|
.IsRequired(false)
|
|
.IsConcurrencyToken(false)
|
|
.ValueGeneratedNever();
|
|
});
|
|
|
|
// Convert DateTimeOffset to ISO 8601 string for SQLite so ORDER BY works
|
|
var converter = new ValueConverter<DateTimeOffset, string>(
|
|
v => v.UtcDateTime.ToString("o"),
|
|
v => DateTimeOffset.Parse(v));
|
|
|
|
var nullableConverter = new ValueConverter<DateTimeOffset?, string?>(
|
|
v => v.HasValue ? v.Value.UtcDateTime.ToString("o") : null,
|
|
v => v != null ? DateTimeOffset.Parse(v) : null);
|
|
|
|
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
|
{
|
|
foreach (var property in entityType.GetProperties())
|
|
{
|
|
if (property.ClrType == typeof(DateTimeOffset))
|
|
{
|
|
property.SetValueConverter(converter);
|
|
property.SetColumnType("TEXT");
|
|
}
|
|
else if (property.ClrType == typeof(DateTimeOffset?))
|
|
{
|
|
property.SetValueConverter(nullableConverter);
|
|
property.SetColumnType("TEXT");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static class SqliteTestHelper
|
|
{
|
|
public static ScadaLinkDbContext CreateInMemoryContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<ScadaLinkDbContext>()
|
|
.UseSqlite("DataSource=:memory:")
|
|
.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
|
|
.Options;
|
|
|
|
var context = new SqliteTestDbContext(options);
|
|
context.Database.OpenConnection();
|
|
context.Database.EnsureCreated();
|
|
return context;
|
|
}
|
|
|
|
public static ScadaLinkDbContext CreateFileContext(string dbPath)
|
|
{
|
|
var options = new DbContextOptionsBuilder<ScadaLinkDbContext>()
|
|
.UseSqlite($"DataSource={dbPath}")
|
|
.ConfigureWarnings(w => w.Ignore(RelationalEventId.PendingModelChangesWarning))
|
|
.Options;
|
|
|
|
var context = new SqliteTestDbContext(options);
|
|
return context;
|
|
}
|
|
}
|