using ZB.MOM.WW.ScadaBridge.Commons.Observability; namespace ZB.MOM.WW.ScadaBridge.Host.Tests; /// /// Infrastructure-free guards for : the meter name is the /// stable value registered with OTel, and the emit helpers are safe to call (they are no-op /// until follow-on tasks wire real emit points and a listener attaches). /// public class ScadaBridgeTelemetryTests { [Fact] public void MeterName_IsStableValue() { Assert.Equal("ZB.MOM.WW.ScadaBridge", ScadaBridgeTelemetry.MeterName); } /// /// Register row 32 class: SetQueueDepthProvider writes the SAME process-global /// slot QueueDepthGaugeCollection (StoreAndForward.Tests) exists to serialize /// access to — this test was mutating it with no clear and no [Collection], /// leaving a stray () => 5 provider registered in the process for the rest of /// this assembly's run. Since the meter/instrument state is a single static field on /// ScadaBridgeTelemetry (Commons), the leak is scoped to whatever process this /// assembly's test host runs in — it cannot reach StoreAndForward.Tests (a separate /// assembly with its own test-host process), but it IS visible within Host.Tests, whose /// MetricsEndpointTests boots a real in-process host (WebApplicationFactory /// shares this AppDomain, not a child process) and scrapes /metrics, which /// enumerates every registered gauge including this one. /// /// Honesty note on what was and wasn't proven: a search of this assembly (and the whole /// test tree) found no test today that asserts on the VALUE of /// scadabridge.store_and_forward.queue.depth from Host.Tests or any other /// same-process reader — MetricsEndpointTests only asserts the endpoint returns /// Prometheus-shaped text, not this series' content, and the only test that DOES assert /// on the value (QueueDepthGaugeTests) lives in StoreAndForward.Tests, a distinct /// test-host process the leak cannot reach. So there is no currently-reproducible flake /// to demonstrate here (no fork of this test with the leak left in has an assertion to /// break) — the defect is a landmine for a FUTURE same-process reader, not a live one. /// The fix (compare-and-clear in finally) closes the landmine regardless; a /// [Collection] is intentionally not added since no second same-process reader of /// this slot exists to serialize against as of this writing — re-run this search before /// removing the guard below if that changes. /// [Fact] public void EmitHelpers_DoNotThrow() { Func queueDepthProvider = () => 5; var ex = Record.Exception(() => { ScadaBridgeTelemetry.RecordDeploymentApplied(); ScadaBridgeTelemetry.RecordInboundApiRequest("X"); ScadaBridgeTelemetry.SiteStreamSubscriberOpened(); ScadaBridgeTelemetry.SiteStreamSubscriberClosed(); ScadaBridgeTelemetry.LiveAlarmAggregatorStarted(); ScadaBridgeTelemetry.LiveAlarmAggregatorStopped(); ScadaBridgeTelemetry.RecordLiveAlarmStreamReconnect(); ScadaBridgeTelemetry.SetQueueDepthProvider(queueDepthProvider); }); try { Assert.Null(ex); } finally { // Compare-and-clear: only removes OUR delegate from the process-global slot, so // a legitimate concurrent registrant (a real StoreAndForwardService started // elsewhere in this process) is never stomped — mirrors // ScadaBridgeTelemetry.ClearQueueDepthProvider's own doc'd contract. ScadaBridgeTelemetry.ClearQueueDepthProvider(queueDepthProvider); } } }