feat: wire SQLite replication between site nodes and fix ConfigurationDatabase tests
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.
This commit is contained in:
@@ -22,12 +22,15 @@ public class ConcurrencyTestDbContext : ScadaLinkDbContext
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Replace the SQL Server RowVersion with an explicit concurrency token for SQLite
|
||||
// Remove the shadow RowVersion property and add a visible ConcurrencyStamp
|
||||
// Replace the SQL Server RowVersion with an explicit concurrency token for SQLite.
|
||||
// SQLite can't auto-generate rowversion, so disable it and use Status as the token instead.
|
||||
modelBuilder.Entity<DeploymentRecord>(builder =>
|
||||
{
|
||||
// The shadow RowVersion property from the base config doesn't work in SQLite.
|
||||
// Instead, use Status as a concurrency token for the test.
|
||||
builder.Property(d => d.RowVersion)
|
||||
.IsRequired(false)
|
||||
.IsConcurrencyToken(false)
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder.Property(d => d.Status).IsConcurrencyToken();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,8 +68,9 @@ public class SecurityRepositoryTests : IDisposable
|
||||
await _repository.SaveChangesAsync();
|
||||
|
||||
var designMappings = await _repository.GetMappingsByRoleAsync("Design");
|
||||
Assert.Single(designMappings);
|
||||
Assert.Equal("Designers", designMappings[0].LdapGroupName);
|
||||
// Seed data includes "SCADA-Designers" with role "Design", plus the one we added
|
||||
Assert.Equal(2, designMappings.Count);
|
||||
Assert.Contains(designMappings, m => m.LdapGroupName == "Designers");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
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 maps DateTimeOffset to a sortable string format for SQLite.
|
||||
/// EF Core 10 SQLite provider does not support ORDER BY on DateTimeOffset columns.
|
||||
/// 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
|
||||
{
|
||||
@@ -19,6 +21,16 @@ public class SqliteTestDbContext : ScadaLinkDbContext
|
||||
{
|
||||
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"),
|
||||
|
||||
@@ -21,13 +21,7 @@ public class DbContextTests : IDisposable
|
||||
|
||||
public DbContextTests()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ScadaLinkDbContext>()
|
||||
.UseSqlite("DataSource=:memory:")
|
||||
.Options;
|
||||
|
||||
_context = new ScadaLinkDbContext(options);
|
||||
_context.Database.OpenConnection();
|
||||
_context.Database.EnsureCreated();
|
||||
_context = SqliteTestHelper.CreateInMemoryContext();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -429,6 +423,6 @@ public class MigrationHelperTests : IDisposable
|
||||
{
|
||||
// Verify the InitialCreate migration is detected as pending
|
||||
var pending = _context.Database.GetPendingMigrations().ToList();
|
||||
Assert.Contains(pending, m => m.Contains("InitialCreate"));
|
||||
Assert.Contains(pending, m => m.Contains("InitialSchema"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user