fix(server): resolve Low code-review findings (Server-004,006,008,012,014,015)

- Server-004: pass the role-derived display name to UserIdentity's base
  ctor (the SDK's DisplayName has no public setter) and drop the dead
  Display property; make RoleBasedIdentity internal sealed.
- Server-006: derive a bounded CancellationToken from the SDK's
  OperationContext.OperationDeadline in OnReadValue / OnWriteValue so a
  stalled driver call can no longer pin the request thread.
- Server-008: mark handled slots via CallMethodRequest.Processed = true
  in RouteScriptedAlarmMethodCalls (the SDK skips on Processed, not on a
  Good error slot).
- Server-012: PeerHttpProbeLoop.ProbeAsync stops mutating client.Timeout
  per call; uses a per-request CancellationTokenSource linked to the
  shutdown token instead.
- Server-014: wire SealedBootstrap into Program.cs via AddSealedBootstrap
  + OpcUaServerService so the generation-sealed cache + stale-config flag
  + resilient reader actually run; /healthz now reflects cache-fallback
  state.
- Server-015: replace the stale 'PR 16 / PR 17 minimum-viable scope'
  class summaries on OtOpcUaServer and OpcUaServerOptions with the
  shipped LDAP + anonymous-role + configurable security-profile prose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-23 07:24:20 -04:00
parent 2b33b64a58
commit 6134050ceb
14 changed files with 698 additions and 40 deletions

View File

@@ -0,0 +1,52 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Configuration.LocalCache;
namespace ZB.MOM.WW.OtOpcUa.Server.Tests;
/// <summary>
/// Regression for Server-014 — <see cref="SealedBootstrap"/> exists in the source tree and
/// is referenced by <c>docs/v2/v2-release-readiness.md</c> as the closed release blocker for
/// generation-sealed config plumbing, but it was never registered in the production DI
/// container. The release blocker remained de-facto open. This test asserts the DI
/// registrations (which <c>Program.cs</c> performs at startup) actually compose: every
/// dependency <see cref="SealedBootstrap"/> needs — <see cref="GenerationSealedCache"/>,
/// <see cref="ResilientConfigReader"/>, <see cref="StaleConfigFlag"/> — must be resolvable
/// so the production wire-up doesn't fail with a missing-service exception at startup.
/// </summary>
[Trait("Category", "Unit")]
public sealed class SealedBootstrapWiringTests
{
[Fact]
public void SealedBootstrap_and_its_dependencies_are_registered_in_DI()
{
var tempRoot = Path.Combine(Path.GetTempPath(), $"otopcua-sealed-bootstrap-wiring-{Guid.NewGuid():N}");
try
{
// Mirror Program.cs's registrations of NodeOptions + the SealedBootstrap chain.
var services = new ServiceCollection();
ZB.MOM.WW.OtOpcUa.Server.ServerWiring.AddSealedBootstrap(services, new NodeOptions
{
NodeId = "test-node",
ClusterId = "test-cluster",
ConfigDbConnectionString = "Server=fake;Database=fake;Integrated Security=true;",
LocalCachePath = tempRoot,
});
services.AddSingleton(NullLoggerFactory.Instance);
services.AddLogging();
using var sp = services.BuildServiceProvider();
sp.GetRequiredService<GenerationSealedCache>().ShouldNotBeNull();
sp.GetRequiredService<ResilientConfigReader>().ShouldNotBeNull();
sp.GetRequiredService<StaleConfigFlag>().ShouldNotBeNull();
sp.GetRequiredService<SealedBootstrap>().ShouldNotBeNull();
}
finally
{
try { if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, recursive: true); } catch { }
}
}
}