513fa1aa16
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.
191 lines
8.4 KiB
C#
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));
|
|
}
|
|
}
|