using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using Shouldly;
using Xunit;
using ZB.MOM.WW.LocalDb.Replication;
using ZB.MOM.WW.OtOpcUa.Commons.Observability;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy;
using ZB.MOM.WW.OtOpcUa.Host.Observability;
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
///
/// scadaproj#13 — ZbTelemetryOptions.Meters / .ActivitySources are strict allowlists,
/// so a meter or activity source the host forgets to name is silently never exported (the Galaxy
/// driver's galaxy.* spans and galaxy.events.* counters were). These tests build the
/// real container, start its
/// providers, and assert on the listener side: a span from a named source is sampled, and an
/// instrument on a named meter is enabled. An unregistered name is the negative control proving
/// the assertion can fail.
///
public sealed class ObservabilityAllowlistTests : IAsyncDisposable
{
private readonly WebApplication _app;
public ObservabilityAllowlistTests()
{
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = [] });
builder.Services.AddOtOpcUaObservability(builder.Configuration);
_app = builder.Build();
// Resolving the providers is what attaches their ActivityListener / MeterListener.
_app.Services.GetRequiredService();
_app.Services.GetRequiredService();
}
[Fact]
public void ObservedLists_NameTheGalaxyDriverMeterAndSource()
{
ObservabilityExtensions.ObservedMeters.ShouldBe(
[OtOpcUaTelemetry.MeterName, LocalDbMetrics.MeterName, GalaxyDriverTelemetryNames.MeterName],
ignoreOrder: true);
ObservabilityExtensions.ObservedActivitySources.ShouldBe(
[OtOpcUaTelemetry.ActivitySourceName, GalaxyDriverTelemetryNames.ActivitySourceName],
ignoreOrder: true);
}
[Theory]
[InlineData(OtOpcUaTelemetry.ActivitySourceName)]
[InlineData(GalaxyDriverTelemetryNames.ActivitySourceName)]
public void SpanFromAnObservedSource_IsRecorded(string sourceName)
{
using var source = new ActivitySource(sourceName);
using var activity = source.StartActivity("allowlist-probe");
activity.ShouldNotBeNull();
activity.IsAllDataRequested.ShouldBeTrue();
}
[Fact]
public void SpanFromAnUnregisteredSource_IsDropped_NegativeControl()
{
using var source = new ActivitySource("ZB.MOM.WW.OtOpcUa.Unregistered." + Guid.NewGuid().ToString("N"));
source.StartActivity("allowlist-probe").ShouldBeNull();
}
[Theory]
[InlineData(OtOpcUaTelemetry.MeterName)]
[InlineData(LocalDbMetrics.MeterName)]
[InlineData(GalaxyDriverTelemetryNames.MeterName)]
public void InstrumentOnAnObservedMeter_IsEnabled(string meterName)
{
using var meter = new Meter(meterName);
var counter = meter.CreateCounter("allowlist.probe." + Guid.NewGuid().ToString("N"));
counter.Enabled.ShouldBeTrue();
}
[Fact]
public void InstrumentOnAnUnregisteredMeter_IsNotEnabled_NegativeControl()
{
using var meter = new Meter("ZB.MOM.WW.OtOpcUa.Unregistered." + Guid.NewGuid().ToString("N"));
meter.CreateCounter("allowlist.probe").Enabled.ShouldBeFalse();
}
public async ValueTask DisposeAsync() => await _app.DisposeAsync();
}