using System.Diagnostics.Metrics;
using ZB.MOM.WW.ScadaBridge.Commons.Observability;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Observability;
///
/// ScadaBridge#81: the instruments added beyond the first set. Each is observed through a
/// filtered to the emitting (test) thread, because the meter is
/// process-global and other tests emit on it concurrently.
///
public sealed class ExtendedInstrumentTests
{
private static List<(long Value, Dictionary Tags)> Capture(string instrument, Action act)
{
var tid = Environment.CurrentManagedThreadId;
var got = new List<(long, Dictionary)>();
using var listener = new MeterListener
{
InstrumentPublished = (i, l) =>
{
if (i.Meter.Name == ScadaBridgeTelemetry.MeterName && i.Name == instrument)
l.EnableMeasurementEvents(i);
}
};
listener.SetMeasurementEventCallback((_, m, tags, _) =>
{
if (Environment.CurrentManagedThreadId != tid) return;
var d = new Dictionary();
foreach (var t in tags) d[t.Key] = t.Value;
got.Add((m, d));
});
listener.Start();
act();
return got;
}
/// A state change emits one transition tagged with the new state; repeating the same state emits none.
[Fact]
public void DataConnectionTransitions_EmitOnlyOnRealStateChange()
{
var name = "xfer-" + Guid.NewGuid().ToString("N");
try
{
var got = Capture("scadabridge.site.data_connections.transitions", () =>
{
ScadaBridgeTelemetry.RecordDataConnectionState(name, ConnectionHealth.Connecting);
ScadaBridgeTelemetry.RecordDataConnectionState(name, ConnectionHealth.Connected);
ScadaBridgeTelemetry.RecordDataConnectionState(name, ConnectionHealth.Connected);
ScadaBridgeTelemetry.RecordDataConnectionState(name, ConnectionHealth.Disconnected);
});
Assert.Equal(
new object?[] { "Connecting", "Connected", "Disconnected" },
got.Select(g => g.Tags["state"]).ToArray());
}
finally { ScadaBridgeTelemetry.RemoveDataConnection(name); }
}
/// Store-and-forward activity is counted by action and category.
[Fact]
public void StoreAndForwardActivity_IsCountedByActionAndCategory()
{
var got = Capture("scadabridge.store_and_forward.activity",
() => ScadaBridgeTelemetry.RecordStoreAndForwardActivity("Parked", "ExternalSystem"));
var m = Assert.Single(got);
Assert.Equal(1, m.Value);
Assert.Equal("Parked", m.Tags["action"]);
Assert.Equal("ExternalSystem", m.Tags["category"]);
}
/// Deployment failures are counted by reason.
[Fact]
public void DeploymentsFailed_IsCountedByReason()
{
var got = Capture("scadabridge.deployments.failed",
() => ScadaBridgeTelemetry.RecordDeploymentFailed("timeout"));
var m = Assert.Single(got);
Assert.Equal("timeout", m.Tags["reason"]);
}
/// A script run emits both the run counter and the duration histogram, clamping negatives.
[Fact]
public void ScriptRun_EmitsCounterAndDuration()
{
var runs = Capture("scadabridge.site.script.runs",
() => ScadaBridgeTelemetry.RecordScriptRun("failed", 5));
Assert.Equal("failed", Assert.Single(runs).Tags["outcome"]);
var durations = Capture("scadabridge.site.script.duration",
() => ScadaBridgeTelemetry.RecordScriptRun("completed", -3));
var d = Assert.Single(durations);
Assert.Equal(0, d.Value);
Assert.Equal("completed", d.Tags["outcome"]);
}
}