Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests/LocalDbReplicationStatusReporterTests.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

191 lines
8.4 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring.Tests;
/// <summary>
/// Deferred-work register row 80 — <see cref="LocalDbReplicationStatusReporter"/> must
/// distinguish "replication is not configured on this node" (null, no reading) from
/// "replication is configured and the peer session is down" (false, a genuine fault).
/// </summary>
/// <remarks>
/// The replication engine is registered unconditionally on every site node and sits inert
/// without a peer, so <c>ISyncStatus</c> answers <c>Connected=false</c> / <c>OplogBacklog=0</c>
/// on a site that was never meant to replicate. Passing that straight through made rig
/// site-b/site-c publish "disconnected, empty backlog" — a healthy unreplicated site reading
/// as a broken replicated one, and central's null branches (conditional KPI emission,
/// sustained-badge anchor exclusion, per register row 33) never receiving the null they exist
/// for. The gate is supplied by the Host from <c>SiteLocalDbSetup.ReplicationIsConfigured</c>,
/// mirroring <c>SiteEventLogReplicationCheck</c>.
/// </remarks>
public class LocalDbReplicationStatusReporterTests
{
private static LocalDbReplicationStatusReporter Build(
SiteHealthCollector collector,
bool replicationConfigured,
bool connected = false,
long? backlog = 0L) =>
new(
connectedProvider: () => connected,
oplogBacklogProvider: () => backlog,
replicationConfiguredProvider: () => replicationConfigured,
collector: collector,
logger: NullLogger<LocalDbReplicationStatusReporter>.Instance,
refreshInterval: TimeSpan.FromHours(1));
/// <summary>Verifies that probing an unconfigured replication node publishes null for both fields.</summary>
[Fact]
public void Probe_ReplicationUnconfigured_PublishesNullForBothFields()
{
// The inert-engine regression. The inert engine's false/0 must NOT reach the report.
var collector = new SiteHealthCollector();
using var reporter = Build(collector, replicationConfigured: false);
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.Null(report.LocalDbReplicationConnected);
Assert.Null(report.LocalDbOplogBacklog);
}
/// <summary>Verifies that probing an unconfigured replication node does not read the sync status providers.</summary>
[Fact]
public void Probe_ReplicationUnconfigured_DoesNotReadTheSyncStatusProviders()
{
// The gate short-circuits rather than reading and discarding: an unconfigured node
// should not be polling a replication engine it never uses. Also guards against a
// future provider that throws when replication is off.
var connectedReads = 0;
var backlogReads = 0;
var collector = new SiteHealthCollector();
using var reporter = new LocalDbReplicationStatusReporter(
connectedProvider: () => { connectedReads++; return false; },
oplogBacklogProvider: () => { backlogReads++; return 0L; },
replicationConfiguredProvider: () => false,
collector: collector,
logger: NullLogger<LocalDbReplicationStatusReporter>.Instance,
refreshInterval: TimeSpan.FromHours(1));
reporter.Probe();
Assert.Equal(0, connectedReads);
Assert.Equal(0, backlogReads);
}
/// <summary>Verifies that a configured but disconnected node publishes false rather than null.</summary>
[Fact]
public void Probe_ReplicationConfiguredButDisconnected_PublishesFalse_NotNull()
{
// The other half of the contract, and the reason the fix is a gate rather than a
// blanket "never report false": on a node that IS configured to replicate, a down
// peer session is a real fault and must stay visible as false.
var collector = new SiteHealthCollector();
using var reporter = Build(
collector, replicationConfigured: true, connected: false, backlog: 4200L);
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.False(report.LocalDbReplicationConnected);
Assert.Equal(4200L, report.LocalDbOplogBacklog);
}
/// <summary>Verifies that a configured and connected node publishes true.</summary>
[Fact]
public void Probe_ReplicationConfiguredAndConnected_PublishesTrue()
{
var collector = new SiteHealthCollector();
using var reporter = Build(
collector, replicationConfigured: true, connected: true, backlog: 0L);
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.True(report.LocalDbReplicationConnected);
Assert.Equal(0L, report.LocalDbOplogBacklog);
}
/// <summary>Verifies that a configured node preserves a null backlog as unknown.</summary>
[Fact]
public void Probe_ReplicationConfigured_PreservesANullBacklogAsUnknown()
{
// A failed oplog poll on a REPLICATED node is "unknown", never 0 — rendering it as a
// healthy empty backlog would report a pair that cannot read its own oplog as fine.
// Connected stays false (the session really is down); only the backlog is unknown.
var collector = new SiteHealthCollector();
using var reporter = Build(
collector, replicationConfigured: true, connected: false, backlog: null);
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.False(report.LocalDbReplicationConnected);
Assert.Null(report.LocalDbOplogBacklog);
}
/// <summary>Verifies that a throwing provider leaves the previous snapshot intact.</summary>
[Fact]
public void Probe_ProviderThrows_LeavesThePreviousSnapshotIntact()
{
// Health reporting must never take the node down, and a failed probe must not
// silently downgrade a good reading to null.
var shouldThrow = false;
var collector = new SiteHealthCollector();
using var reporter = new LocalDbReplicationStatusReporter(
connectedProvider: () => shouldThrow
? throw new InvalidOperationException("sync status unavailable")
: true,
oplogBacklogProvider: () => 7L,
replicationConfiguredProvider: () => true,
collector: collector,
logger: NullLogger<LocalDbReplicationStatusReporter>.Instance,
refreshInterval: TimeSpan.FromHours(1));
reporter.Probe();
shouldThrow = true;
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.True(report.LocalDbReplicationConnected);
Assert.Equal(7L, report.LocalDbOplogBacklog);
}
/// <summary>Verifies that flipping the replication gate on stops the null reporting.</summary>
[Fact]
public void Probe_GateFlippedOn_StopsReportingNull()
{
// Turning replication on for a previously-unreplicated site is a both-nodes-together
// restart in practice, but the gate is read per probe, so pin that it is not latched.
var configured = false;
var collector = new SiteHealthCollector();
using var reporter = new LocalDbReplicationStatusReporter(
connectedProvider: () => true,
oplogBacklogProvider: () => 12L,
replicationConfiguredProvider: () => configured,
collector: collector,
logger: NullLogger<LocalDbReplicationStatusReporter>.Instance,
refreshInterval: TimeSpan.FromHours(1));
reporter.Probe();
Assert.Null(collector.CollectReport("TestSite").LocalDbReplicationConnected);
configured = true;
reporter.Probe();
var report = collector.CollectReport("TestSite");
Assert.True(report.LocalDbReplicationConnected);
Assert.Equal(12L, report.LocalDbOplogBacklog);
}
/// <summary>Verifies that a null replication-configured provider causes the constructor to throw.</summary>
[Fact]
public void Constructor_NullReplicationConfiguredProvider_Throws()
{
Assert.Throws<ArgumentNullException>(() => new LocalDbReplicationStatusReporter(
connectedProvider: () => false,
oplogBacklogProvider: () => 0L,
replicationConfiguredProvider: null!,
collector: new SiteHealthCollector(),
logger: NullLogger<LocalDbReplicationStatusReporter>.Instance));
}
}