feat(localdb): create config + sf_messages tables in the consolidated DB
Task 7. SiteLocalDbSetup.OnReady now applies SiteStorageSchema and StoreAndForwardSchema alongside the Phase 1 schemas, so the nine site configuration tables and the store-and-forward buffer live in the consolidated LocalDb file. Deliberately NOT registered for replication. The bespoke SiteReplicationActor and the StoreAndForward ReplicationService still own these tables until the Task 14 cutover deletes both and registers them in one commit; registering early would run two replicators over the same rows and let either one's defects hide behind the other's writes. Pinned by two tests through the real composition root: one asserting all twelve tables exist, one asserting ReplicatedTables is EXACTLY the Phase 1 pair. Non-vacuity verified by removing the DDL and observing the failure. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using ZB.MOM.WW.LocalDb;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
|
||||
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Tracking;
|
||||
using ZB.MOM.WW.ScadaBridge.StoreAndForward;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Host;
|
||||
|
||||
@@ -44,6 +46,12 @@ public static class SiteLocalDbSetup
|
||||
{
|
||||
OperationTrackingSchema.Apply(connection);
|
||||
SiteEventLogSchema.Apply(connection);
|
||||
|
||||
// Phase 2: the site's configuration tables and the store-and-forward buffer
|
||||
// now live in this file too. Created here, deliberately NOT registered — see
|
||||
// below.
|
||||
SiteStorageSchema.Apply(connection);
|
||||
StoreAndForwardSchema.Apply(connection);
|
||||
}
|
||||
|
||||
// Both tables qualify: each has an explicit primary key (RegisterReplicated
|
||||
@@ -52,6 +60,13 @@ public static class SiteLocalDbSetup
|
||||
db.RegisterReplicated("OperationTracking");
|
||||
db.RegisterReplicated("site_events");
|
||||
|
||||
// The Phase 2 tables are created above but NOT registered yet. The bespoke
|
||||
// SiteReplicationActor / StoreAndForward ReplicationService still own replicating
|
||||
// them until the Task 14 cutover deletes both and registers these in one commit.
|
||||
// Registering them now would mean two independent replicators writing the same
|
||||
// rows — harmless in principle (both upsert) but it would mask a defect in either
|
||||
// one, which is exactly what the cutover needs to be able to see.
|
||||
|
||||
// AFTER registration, so migrated rows enter the oplog and reach the peer like
|
||||
// any other write. Before it, they would be invisible to replication forever.
|
||||
SiteLocalDbLegacyMigrator.Migrate(db, config);
|
||||
|
||||
@@ -215,6 +215,62 @@ public class SiteLocalDbWiringTests : IDisposable
|
||||
Assert.Contains(LocalDbMetrics.MeterName, SiteServiceRegistration.ObservedMeters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Site_LocalDb_CreatesThePhase2Tables_InTheConsolidatedFile()
|
||||
{
|
||||
// Phase 2 (Task 7). The nine configuration tables and the store-and-forward buffer
|
||||
// now live in the consolidated file rather than in scadabridge.db and
|
||||
// store-and-forward.db. Asserting through the REAL composition root is what proves
|
||||
// OnReady applies both schemas — the stores themselves also call Apply, so a test
|
||||
// that went through a store would pass even if OnReady never touched them.
|
||||
var db = _host.Services.GetRequiredService<ILocalDb>();
|
||||
|
||||
var tables = TableNames(db);
|
||||
|
||||
// One from each Phase 1 schema, then every Phase 2 table.
|
||||
Assert.Contains("OperationTracking", tables);
|
||||
Assert.Contains("site_events", tables);
|
||||
Assert.Contains("sf_messages", tables);
|
||||
foreach (var table in new[]
|
||||
{
|
||||
"deployed_configurations", "static_attribute_overrides", "shared_scripts",
|
||||
"external_systems", "database_connections", "notification_lists",
|
||||
"data_connection_definitions", "smtp_configurations", "native_alarm_state",
|
||||
})
|
||||
{
|
||||
Assert.Contains(table, tables);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Site_LocalDb_DoesNotYetRegisterThePhase2Tables()
|
||||
{
|
||||
// The "not yet" is the assertion that matters, and it is why this is an EXACTLY
|
||||
// check rather than a Contains. Until the Task 14 cutover, the bespoke
|
||||
// SiteReplicationActor and the StoreAndForward ReplicationService still own these
|
||||
// tables. Registering them early would run two replicators over the same rows and
|
||||
// hide a defect in either one behind the other's writes.
|
||||
//
|
||||
// When Task 14 lands, this test does not get deleted — it gets inverted.
|
||||
var db = _host.Services.GetRequiredService<ILocalDb>();
|
||||
|
||||
Assert.Equal(
|
||||
["OperationTracking", "site_events"],
|
||||
db.ReplicatedTables.Keys.OrderBy(k => k, StringComparer.Ordinal).ToArray());
|
||||
}
|
||||
|
||||
private static HashSet<string> TableNames(ILocalDb db)
|
||||
{
|
||||
using var connection = db.CreateConnection();
|
||||
using var cmd = connection.CreateCommand();
|
||||
cmd.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table'";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
|
||||
var names = new HashSet<string>(StringComparer.Ordinal);
|
||||
while (reader.Read()) names.Add(reader.GetString(0));
|
||||
return names;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Site_LocalDb_CreatesTheConfiguredFile()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user