feat(localdb): rewire OperationTrackingStore onto the consolidated site database
Task 4 of the LocalDb Phase 1 adoption plan. OperationTrackingStore took IOptions<OperationTrackingOptions> and opened its own SqliteConnection from ConnectionString. It now takes ILocalDb and gets every connection - writer and the two ad-hoc reader paths - from CreateConnection(). This is not cosmetic. OperationTracking is a RegisterReplicated table, so its capture triggers call zb_hlc_next(). That UDF is registered per connection by ILocalDb and by nothing else, so a raw SqliteConnection would fail closed on every write. Connections from CreateConnection() also arrive already open - calling Open()/OpenAsync() on one throws - hence the removed OpenAsync calls on the reader paths. OperationTrackingOptions.ConnectionString is now vestigial for this store; the database location is LocalDb:Path. The options class stays (retention settings) and the config key stays bound for the site config DB. InitializeSchema is kept but is now always a no-op in the host: onReady runs while ILocalDb is being constructed, strictly before this constructor can receive it. It remains so a directly-constructed store (tests, tooling) is self-sufficient. Tests: the store fixture moves off mode=memory&cache=shared onto a real ILocalDb over a temp file. There is no in-memory mode - LocalDbOptions.Path is a filesystem path - and testing through a raw in-memory connection would no longer resemble the host. Verifier connections stay raw on purpose: this fixture never registers the table, so no trigger fires and the UDF is never reached. Three DI fixtures needed LocalDb:Path added, because resolving IOperationTrackingStore now forces ILocalDb construction: SiteCompositionRootTests, SiteAuditWiringTests, and the AuditLog CombinedTelemetryHarness. Verified: build 0 warnings; Host 294/294, SiteRuntime 530/530, AuditLog 354/354. Claude-Session: https://claude.ai/code/session_01BL2Vu1ESDQ9SCN4gVKkdts
This commit is contained in:
@@ -383,11 +383,13 @@ public class SiteCompositionRootTests : IDisposable
|
||||
private readonly WebApplication _host;
|
||||
private readonly string _tempDbPath;
|
||||
private readonly string _tempTrackingDbPath;
|
||||
private readonly string _tempLocalDbPath;
|
||||
|
||||
public SiteCompositionRootTests()
|
||||
{
|
||||
_tempDbPath = Path.Combine(Path.GetTempPath(), $"scadabridge_test_{Guid.NewGuid()}.db");
|
||||
_tempTrackingDbPath = Path.Combine(Path.GetTempPath(), $"scadabridge_track_{Guid.NewGuid()}.db");
|
||||
_tempLocalDbPath = Path.Combine(Path.GetTempPath(), $"scadabridge_localdb_{Guid.NewGuid()}.db");
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Configuration.Sources.Clear();
|
||||
@@ -400,10 +402,13 @@ public class SiteCompositionRootTests : IDisposable
|
||||
["ScadaBridge:Node:RemotingPort"] = "0",
|
||||
["ScadaBridge:Node:GrpcPort"] = "0",
|
||||
["ScadaBridge:Database:SiteDbPath"] = _tempDbPath,
|
||||
// Point the site-local operation-tracking SQLite store (the ctor opens/creates
|
||||
// the file eagerly) at a temp path so resolving IOperationTrackingStore in the
|
||||
// composition-root test does not litter site-tracking.db in the test cwd.
|
||||
// Retained for the site config DB; it no longer points the tracking store
|
||||
// anywhere — that store now opens the consolidated database below.
|
||||
["ScadaBridge:OperationTracking:ConnectionString"] = $"Data Source={_tempTrackingDbPath}",
|
||||
// The consolidated site database. IOperationTrackingStore resolves ILocalDb
|
||||
// and creates the file eagerly in its ctor, so this must be a temp path or
|
||||
// the composition-root test litters the working directory.
|
||||
["LocalDb:Path"] = _tempLocalDbPath,
|
||||
// ClusterOptions requires at least one seed node (ClusterOptionsValidator).
|
||||
["ScadaBridge:Cluster:SeedNodes:0"] = "akka.tcp://scadabridge@localhost:2551",
|
||||
["ScadaBridge:Cluster:SeedNodes:1"] = "akka.tcp://scadabridge@localhost:2552",
|
||||
@@ -427,6 +432,7 @@ public class SiteCompositionRootTests : IDisposable
|
||||
(_host as IDisposable)?.Dispose();
|
||||
try { File.Delete(_tempDbPath); } catch { /* best effort */ }
|
||||
try { File.Delete(_tempTrackingDbPath); } catch { /* best effort */ }
|
||||
try { File.Delete(_tempLocalDbPath); } catch { /* best effort */ }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user