Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/ObservabilityResourceIdentityTests.cs
T
Joseph Doherty 412da27c5f
v2-ci / unit-tests (push) Has been cancelled
feat(telemetry): Telemetry 0.3.0 - service.version, site.id/node.role, logs over OTLP (scadaproj#55)
Pins ZB.MOM.WW.Telemetry(.Serilog) 0.1.0 -> 0.3.0. The OTel Resource now names the
node: site.id = its application Cluster id (the cluster-<ClusterId> role suffix,
omitted on a legacy node), node.role = its plain roles in canonical order
(admin / driver / admin,driver), service.version = the entry assembly's informational
version (the git SHA). Program passes its resolved EffectiveRoles. The whole
OtOpcUa:Telemetry section goes through the shared ApplyConfiguration, so SiteId /
NodeRole / ServiceVersion / ResourceAttributes:* are operator overrides, and
Exporter=Otlp now also sends the Serilog logs to the same collector.

ObservabilityResourceIdentityTests cover the mapping and the built container's
Resource. Host.Tests 288/288, Host.IntegrationTests 325/327 (2 skipped).

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
2026-11-12 21:13:23 -05:00

72 lines
2.6 KiB
C#

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry;
using OpenTelemetry.Trace;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Cluster;
using ZB.MOM.WW.OtOpcUa.Host.Observability;
using ZB.MOM.WW.Telemetry;
namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
/// <summary>
/// scadaproj#55 — the OTel Resource carries <c>site.id</c> (the node's application Cluster id),
/// <c>node.role</c> (its plain roles) and <c>service.version</c> (entry-assembly default), so two
/// OtOpcUa nodes are distinguishable by more than <c>service.instance.id</c>.
/// </summary>
public sealed class ObservabilityResourceIdentityTests
{
private static IConfiguration EmptyConfig() => new ConfigurationBuilder().Build();
[Fact]
public void FusedNode_ReportsClusterIdAndCanonicalRoles()
{
var roles = EffectiveRoles.Resolve(["driver", "cluster-SITE-A", "admin"], envRoles: null);
var o = new ZbTelemetryOptions();
ObservabilityExtensions.ConfigureTelemetry(o, EmptyConfig(), roles);
o.SiteId.ShouldBe("SITE-A");
o.NodeRole.ShouldBe("admin,driver");
o.ServiceVersion.ShouldBeNull(); // null = the library's entry-assembly default
}
[Fact]
public void LegacyNodeWithoutClusterRole_OmitsSiteId()
{
var roles = EffectiveRoles.Resolve(["driver"], envRoles: null);
var o = new ZbTelemetryOptions();
ObservabilityExtensions.ConfigureTelemetry(o, EmptyConfig(), roles);
o.SiteId.ShouldBeNull();
o.NodeRole.ShouldBe("driver");
}
[Fact]
public void NoRoles_OmitsNodeRole()
{
ObservabilityExtensions.NodeRoleOf(EffectiveRoles.Resolve(null, null)).ShouldBeNull();
}
[Fact]
public void BuiltContainer_ResourceCarriesIdentityAndVersion()
{
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = [] });
builder.Services.AddOtOpcUaObservability(
builder.Configuration, EffectiveRoles.Resolve(["admin", "driver", "cluster-MAIN"], null));
using var app = builder.Build();
var attributes = app.Services.GetRequiredService<TracerProvider>().GetResource().Attributes
.ToDictionary(a => a.Key, a => a.Value);
attributes["service.name"].ShouldBe("otopcua");
attributes["site.id"].ShouldBe("MAIN");
attributes["node.role"].ShouldBe("admin,driver");
attributes["service.version"].ShouldBe(ZbResource.DefaultServiceVersion);
((string)attributes["service.version"]).ShouldNotBeNullOrWhiteSpace();
}
}