Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/TestSupport/DeploymentManagerStartupProbe.cs
T
Joseph Doherty c4186509f6 refactor(health): move ISiteHealthCollector to Commons; Communication drops HealthMonitoring reference (ScadaBridge#87)
Interface now lives in Commons.Interfaces.Services; AuditLog no longer gets it
transitively through Communication. Implementation stays in HealthMonitoring.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:13:58 -05:00

92 lines
3.5 KiB
C#

using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.HealthMonitoring;
namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.TestSupport;
/// <summary>
/// Minimal <see cref="ISiteHealthCollector"/> whose only job is to expose the observable edge
/// "the Deployment Manager's boot-time config load has PROVABLY read storage". The manager calls
/// <see cref="SetInstanceCounts"/> unconditionally the moment that load finishes — including for
/// an empty site — so a non-zero <see cref="ReportCount"/> replaces a wall-clock sleep.
///
/// <para>
/// <b>Why every post-boot start-path test must gate on this (deferred register rows 83 + 78).</b>
/// A test that writes a <c>deployed_configurations</c> row BEFORE that read lands has its row
/// picked up by the boot load itself — so the test passes while proving the boot load works,
/// not the post-boot path (startup reconcile, sweep, enable) it claims to cover. A bare
/// <c>Task.Delay</c> only makes that race unlikely, not impossible: on a loaded machine the load
/// can still be in flight when the delay expires, and the test silently changes what it proves
/// instead of failing. Gating on the first instance-count report removes the race entirely.
/// </para>
/// </summary>
public sealed class DeploymentManagerStartupProbe : ISiteHealthCollector
{
private int _reports;
/// <summary>
/// Number of <see cref="SetInstanceCounts"/> calls seen. Greater than zero means the
/// boot-time config load has completed and read storage.
/// </summary>
public int ReportCount => Volatile.Read(ref _reports);
/// <inheritdoc />
public void SetInstanceCounts(int deployed, int enabled, int disabled) =>
Interlocked.Increment(ref _reports);
/// <inheritdoc />
public void IncrementScriptError() { }
/// <inheritdoc />
public void IncrementAlarmError() { }
/// <inheritdoc />
public void IncrementDeadLetter() { }
/// <inheritdoc />
public void IncrementSiteAuditWriteFailures() { }
/// <inheritdoc />
public void IncrementAuditRedactionFailure() { }
/// <inheritdoc />
public void UpdateSiteAuditBacklog(Commons.Types.SiteAuditBacklogSnapshot snapshot) { }
/// <inheritdoc />
public void UpdateConnectionHealth(string connectionName, ConnectionHealth health) { }
/// <inheritdoc />
public void RemoveConnection(string connectionName) { }
/// <inheritdoc />
public void UpdateTagResolution(string connectionName, int totalSubscribed, int successfullyResolved) { }
/// <inheritdoc />
public void UpdateConnectionEndpoint(string connectionName, string endpoint) { }
/// <inheritdoc />
public void UpdateTagQuality(string connectionName, int good, int bad, int uncertain) { }
/// <inheritdoc />
public void SetStoreAndForwardDepths(IReadOnlyDictionary<string, int> depths) { }
/// <inheritdoc />
public void SetParkedMessageCount(int count) { }
/// <inheritdoc />
public void SetNodeHostname(string hostname) { }
/// <inheritdoc />
public void SetClusterNodes(IReadOnlyList<Commons.Messages.Health.NodeStatus> nodes) { }
/// <inheritdoc />
public void SetActiveNode(bool isActive) { }
/// <inheritdoc />
public bool IsActiveNode => true;
/// <inheritdoc />
public Commons.Messages.Health.SiteHealthReport CollectReport(string siteId) =>
throw new NotSupportedException("Startup probe collects no report.");
}