Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Tracking/OperationTrackingSchemaTests.cs
T
Joseph Doherty 513fa1aa16 docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
2026-10-07 15:05:49 -04:00

154 lines
5.6 KiB
C#

using Microsoft.Data.Sqlite;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Tracking;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Tracking;
/// <summary>
/// LocalDb Phase 1 — the <c>OperationTracking</c> DDL is extracted out of
/// <see cref="OperationTrackingStore"/> so it can also be applied from the Host's
/// <c>AddZbLocalDb</c> onReady callback, which owns table creation for the consolidated
/// site database. These tests pin the extracted schema against the shape the store has
/// always created: the move must be behaviour-preserving.
/// </summary>
public class OperationTrackingSchemaTests
{
private static SqliteConnection OpenConnection()
{
var conn = new SqliteConnection("Data Source=:memory:");
conn.Open();
return conn;
}
private static List<(string Name, string Type, bool NotNull, bool Pk)> TableInfo(
SqliteConnection conn, string table)
{
using var cmd = conn.CreateCommand();
cmd.CommandText = $"PRAGMA table_info('{table}')";
using var reader = cmd.ExecuteReader();
var cols = new List<(string, string, bool, bool)>();
while (reader.Read())
{
cols.Add((reader.GetString(1), reader.GetString(2), reader.GetInt32(3) == 1,
reader.GetInt32(5) > 0));
}
return cols;
}
/// <summary>Verifies that applying the schema creates the table with the expected columns.</summary>
[Fact]
public void Apply_CreatesTableWithExpectedColumns()
{
using var conn = OpenConnection();
OperationTrackingSchema.Apply(conn);
var cols = TableInfo(conn, "OperationTracking");
Assert.Equal(
[
"TrackedOperationId", "Kind", "TargetSummary", "Status", "RetryCount",
"LastError", "HttpStatus", "CreatedAtUtc", "UpdatedAtUtc", "TerminalAtUtc",
"SourceInstanceId", "SourceScript", "SourceNode"
],
cols.Select(c => c.Name));
}
/// <summary>Verifies that the schema uses a text primary key.</summary>
[Fact]
public void Apply_UsesTextPrimaryKey()
{
using var conn = OpenConnection();
OperationTrackingSchema.Apply(conn);
// RegisterReplicated requires an explicit primary key and rejects BLOB
// columns; a TEXT PK is what makes this table replicate as-is.
var pk = Assert.Single(TableInfo(conn, "OperationTracking"), c => c.Pk);
Assert.Equal("TrackedOperationId", pk.Name);
Assert.Equal("TEXT", pk.Type);
}
/// <summary>Verifies that the schema has no blob columns.</summary>
[Fact]
public void Apply_HasNoBlobColumns()
{
using var conn = OpenConnection();
OperationTrackingSchema.Apply(conn);
// ILocalDb.RegisterReplicated throws on any column whose declared type
// contains BLOB (json_object cannot capture them).
Assert.DoesNotContain(
TableInfo(conn, "OperationTracking"),
c => c.Type.Contains("BLOB", StringComparison.OrdinalIgnoreCase));
}
/// <summary>Verifies that applying the schema creates the expected indexes.</summary>
[Fact]
public void Apply_CreatesExpectedIndexes()
{
using var conn = OpenConnection();
OperationTrackingSchema.Apply(conn);
using var cmd = conn.CreateCommand();
cmd.CommandText =
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'OperationTracking' " +
"AND name NOT LIKE 'sqlite_%' ORDER BY name";
using var reader = cmd.ExecuteReader();
var indexes = new List<string>();
while (reader.Read()) indexes.Add(reader.GetString(0));
Assert.Equal(
["IX_OperationTracking_Status_Updated", "IX_OperationTracking_UpdatedAt"],
indexes);
}
/// <summary>Verifies that applying the schema repeatedly is idempotent.</summary>
[Fact]
public void Apply_IsIdempotent()
{
using var conn = OpenConnection();
OperationTrackingSchema.Apply(conn);
OperationTrackingSchema.Apply(conn);
Assert.Equal(13, TableInfo(conn, "OperationTracking").Count);
}
/// <summary>Verifies that applying the schema adds the SourceNode column to a legacy table that lacks it.</summary>
[Fact]
public void Apply_AddsSourceNodeToLegacyTableMissingIt()
{
using var conn = OpenConnection();
// A tracking DB created by a pre-SourceNode build. CREATE TABLE IF NOT
// EXISTS will not add the column, so Apply must ALTER it in — the
// additive-migration behaviour the store has today must survive extraction.
using (var legacy = conn.CreateCommand())
{
legacy.CommandText = """
CREATE TABLE OperationTracking (
TrackedOperationId TEXT NOT NULL PRIMARY KEY,
Kind TEXT NOT NULL,
TargetSummary TEXT NULL,
Status TEXT NOT NULL,
RetryCount INTEGER NOT NULL DEFAULT 0,
LastError TEXT NULL,
HttpStatus INTEGER NULL,
CreatedAtUtc TEXT NOT NULL,
UpdatedAtUtc TEXT NOT NULL,
TerminalAtUtc TEXT NULL,
SourceInstanceId TEXT NULL,
SourceScript TEXT NULL
);
""";
legacy.ExecuteNonQuery();
}
OperationTrackingSchema.Apply(conn);
Assert.Contains(TableInfo(conn, "OperationTracking"), c => c.Name == "SourceNode");
}
}