refactor(browse): rename BrowseOpcUaNode* to protocol-agnostic BrowseNode*

Renames BrowseOpcUaNodeCommand/Result -> BrowseNodeCommand/Result and
CommunicationService.BrowseOpcUaNodeAsync -> BrowseNodeAsync across Commons,
Communication, SiteRuntime, DCL actors, and CentralUI. Wire manifest name
follows (BrowseOpcUaNode -> BrowseNode). Browse regression tests green.
This commit is contained in:
Joseph Doherty
2026-05-29 07:57:36 -04:00
parent 20c24ef260
commit 9b7916bb2e
14 changed files with 52 additions and 53 deletions
-1
View File
@@ -21,7 +21,6 @@ This document serves as the master index for the SCADA system design. The system
### Scale ### Scale
- ~10 site clusters, each with 50500 machines, 2575 live tags per machine.
- Central cluster: 2-node active/standby behind a load balancer. - Central cluster: 2-node active/standby behind a load balancer.
- Site clusters: 2-node active/standby, headless (no UI). - Site clusters: 2-node active/standby, headless (no UI).
@@ -1,11 +1,11 @@
{ {
"planPath": "docs/plans/2026-05-28-mxgateway-data-connection.md", "planPath": "docs/plans/2026-05-28-mxgateway-data-connection.md",
"tasks": [ "tasks": [
{"id": 6, "planTask": 1, "subject": "Task 1: Packaging foundation (Gitea feed + package refs)", "status": "pending"}, {"id": 6, "planTask": 1, "subject": "Task 1: Packaging foundation (Gitea feed + package refs)", "status": "completed"},
{"id": 7, "planTask": 2, "subject": "Task 2: MxGatewayEndpointConfig type", "status": "pending"}, {"id": 7, "planTask": 2, "subject": "Task 2: MxGatewayEndpointConfig type", "status": "completed"},
{"id": 8, "planTask": 3, "subject": "Task 3: MxGatewayEndpointConfigSerializer + tests", "status": "pending", "blockedBy": [7]}, {"id": 8, "planTask": 3, "subject": "Task 3: MxGatewayEndpointConfigSerializer + tests", "status": "pending", "blockedBy": [7]},
{"id": 9, "planTask": 4, "subject": "Task 4: MxGatewayEndpointConfigValidator + tests", "status": "pending", "blockedBy": [7]}, {"id": 9, "planTask": 4, "subject": "Task 4: MxGatewayEndpointConfigValidator + tests", "status": "pending", "blockedBy": [7]},
{"id": 10, "planTask": 5, "subject": "Task 5: Client seam interfaces + MxGatewayGlobalOptions", "status": "pending"}, {"id": 10, "planTask": 5, "subject": "Task 5: Client seam interfaces + MxGatewayGlobalOptions", "status": "completed"},
{"id": 11, "planTask": 6, "subject": "Task 6: Adapter connect/disconnect/Disconnected + value mapping", "status": "pending", "blockedBy": [7, 10]}, {"id": 11, "planTask": 6, "subject": "Task 6: Adapter connect/disconnect/Disconnected + value mapping", "status": "pending", "blockedBy": [7, 10]},
{"id": 12, "planTask": 7, "subject": "Task 7: Adapter subscribe/unsubscribe + event routing", "status": "pending", "blockedBy": [11]}, {"id": 12, "planTask": 7, "subject": "Task 7: Adapter subscribe/unsubscribe + event routing", "status": "pending", "blockedBy": [11]},
{"id": 13, "planTask": 8, "subject": "Task 8: Adapter read/write batch + error classification", "status": "pending", "blockedBy": [11]}, {"id": 13, "planTask": 8, "subject": "Task 8: Adapter read/write batch + error classification", "status": "pending", "blockedBy": [11]},
@@ -50,7 +50,7 @@ public static class ServiceCollectionExtensions
// Backs the Audit Log page's Export button via GET /api/centralui/audit/export. // Backs the Audit Log page's Export button via GET /api/centralui/audit/export.
services.AddScoped<IAuditLogExportService, AuditLogExportService>(); services.AddScoped<IAuditLogExportService, AuditLogExportService>();
// OPC UA Tag Browser (Task 14): facade over CommunicationService.BrowseOpcUaNodeAsync // OPC UA Tag Browser (Task 14): facade over CommunicationService.BrowseNodeAsync
// that enforces the CentralUI-side Design-role trust boundary and translates // that enforces the CentralUI-side Design-role trust boundary and translates
// transport failures into typed BrowseFailure results for the dialog. // transport failures into typed BrowseFailure results for the dialog.
services.AddScoped<IOpcUaBrowseService, OpcUaBrowseService>(); services.AddScoped<IOpcUaBrowseService, OpcUaBrowseService>();
@@ -6,7 +6,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
/// CentralUI facade over the central-to-site OPC UA browse command. Backs the /// CentralUI facade over the central-to-site OPC UA browse command. Backs the
/// OPC UA Tag Browser dialog: each tree expansion / manual node-id paste calls /// OPC UA Tag Browser dialog: each tree expansion / manual node-id paste calls
/// <see cref="BrowseChildrenAsync"/>, which forwards a /// <see cref="BrowseChildrenAsync"/>, which forwards a
/// <see cref="BrowseOpcUaNodeCommand"/> to the owning site via /// <see cref="BrowseNodeCommand"/> to the owning site via
/// <see cref="ZB.MOM.WW.ScadaBridge.Communication.CommunicationService"/>. /// <see cref="ZB.MOM.WW.ScadaBridge.Communication.CommunicationService"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
@@ -29,7 +29,7 @@ public interface IOpcUaBrowseService
/// <param name="connectionName">Name of the site-local data connection to browse against — the site's <c>DataConnectionManagerActor</c> indexes its children by name.</param> /// <param name="connectionName">Name of the site-local data connection to browse against — the site's <c>DataConnectionManagerActor</c> indexes its children by name.</param>
/// <param name="parentNodeId">Node to browse, or <c>null</c> to browse from the server root.</param> /// <param name="parentNodeId">Node to browse, or <c>null</c> to browse from the server root.</param>
/// <param name="cancellationToken">Cancellation token.</param> /// <param name="cancellationToken">Cancellation token.</param>
Task<BrowseOpcUaNodeResult> BrowseChildrenAsync( Task<BrowseNodeResult> BrowseChildrenAsync(
string siteId, string siteId,
string connectionName, string connectionName,
string? parentNodeId, string? parentNodeId,
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
/// <summary> /// <summary>
/// Default <see cref="IOpcUaBrowseService"/> implementation — a thin facade over /// Default <see cref="IOpcUaBrowseService"/> implementation — a thin facade over
/// <see cref="CommunicationService.BrowseOpcUaNodeAsync"/> that enforces the /// <see cref="CommunicationService.BrowseNodeAsync"/> that enforces the
/// CentralUI-side <c>Design</c>-role trust boundary and translates transport /// CentralUI-side <c>Design</c>-role trust boundary and translates transport
/// exceptions into a typed <see cref="BrowseFailure"/> result. /// exceptions into a typed <see cref="BrowseFailure"/> result.
/// </summary> /// </summary>
@@ -36,7 +36,7 @@ public sealed class OpcUaBrowseService : IOpcUaBrowseService
} }
/// <inheritdoc/> /// <inheritdoc/>
public async Task<BrowseOpcUaNodeResult> BrowseChildrenAsync( public async Task<BrowseNodeResult> BrowseChildrenAsync(
string siteId, string siteId,
string connectionName, string connectionName,
string? parentNodeId, string? parentNodeId,
@@ -47,7 +47,7 @@ public sealed class OpcUaBrowseService : IOpcUaBrowseService
var state = await _auth.GetAuthenticationStateAsync(); var state = await _auth.GetAuthenticationStateAsync();
if (!state.User.HasClaim(JwtTokenService.RoleClaimType, "Design")) if (!state.User.HasClaim(JwtTokenService.RoleClaimType, "Design"))
{ {
return new BrowseOpcUaNodeResult( return new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure(BrowseFailureKind.ServerError, "Not authorized.")); new BrowseFailure(BrowseFailureKind.ServerError, "Not authorized."));
@@ -55,9 +55,9 @@ public sealed class OpcUaBrowseService : IOpcUaBrowseService
try try
{ {
return await _communication.BrowseOpcUaNodeAsync( return await _communication.BrowseNodeAsync(
siteId, siteId,
new BrowseOpcUaNodeCommand(connectionName, parentNodeId), new BrowseNodeCommand(connectionName, parentNodeId),
cancellationToken); cancellationToken);
} }
catch (TimeoutException ex) catch (TimeoutException ex)
@@ -65,7 +65,7 @@ public sealed class OpcUaBrowseService : IOpcUaBrowseService
// Akka Ask timed out — the site (or its OPC UA session) didn't answer // Akka Ask timed out — the site (or its OPC UA session) didn't answer
// within CommunicationOptions.QueryTimeout. Surface as a typed // within CommunicationOptions.QueryTimeout. Surface as a typed
// Timeout failure so the dialog can render an inline banner. // Timeout failure so the dialog can render an inline banner.
return new BrowseOpcUaNodeResult( return new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure(BrowseFailureKind.Timeout, ex.Message)); new BrowseFailure(BrowseFailureKind.Timeout, ex.Message));
@@ -80,7 +80,7 @@ public sealed class OpcUaBrowseService : IOpcUaBrowseService
{ {
// Any other transport / serialization failure: keep the dialog // Any other transport / serialization failure: keep the dialog
// alive and let the user fall back to manual node-id paste. // alive and let the user fall back to manual node-id paste.
return new BrowseOpcUaNodeResult( return new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure(BrowseFailureKind.ServerError, ex.Message)); new BrowseFailure(BrowseFailureKind.ServerError, ex.Message));
@@ -15,11 +15,11 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// </remarks> /// </remarks>
/// <param name="ConnectionName">Name of the site-local data connection to browse against.</param> /// <param name="ConnectionName">Name of the site-local data connection to browse against.</param>
/// <param name="ParentNodeId">Node to browse, or null to browse from the server root (ObjectsFolder).</param> /// <param name="ParentNodeId">Node to browse, or null to browse from the server root (ObjectsFolder).</param>
public record BrowseOpcUaNodeCommand( public record BrowseNodeCommand(
string ConnectionName, string ConnectionName,
string? ParentNodeId); string? ParentNodeId);
public record BrowseOpcUaNodeResult( public record BrowseNodeResult(
IReadOnlyList<BrowseNode> Children, IReadOnlyList<BrowseNode> Children,
bool Truncated, bool Truncated,
BrowseFailure? Failure); BrowseFailure? Failure);
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Keyed by <see cref="ConnectionName"/> (not id) for the same reason as /// Keyed by <see cref="ConnectionName"/> (not id) for the same reason as
/// <see cref="BrowseOpcUaNodeCommand"/>: the site-side /// <see cref="BrowseNodeCommand"/>: the site-side
/// <c>DataConnectionManagerActor</c> indexes its children by connection name, /// <c>DataConnectionManagerActor</c> indexes its children by connection name,
/// and the central UI already has the connection name in scope from the /// and the central UI already has the connection name in scope from the
/// bindings table. The central <c>DataConnections</c> table's id is not /// bindings table. The central <c>DataConnections</c> table's id is not
@@ -152,10 +152,10 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers
// DataConnectionActor children (which own the live OPC UA sessions) // DataConnectionActor children (which own the live OPC UA sessions)
// only exist on the singleton's node. The singleton then re-forwards // only exist on the singleton's node. The singleton then re-forwards
// to its own /user/dcl-manager, which DOES have the connection. // to its own /user/dcl-manager, which DOES have the connection.
Receive<BrowseOpcUaNodeCommand>(msg => _deploymentManagerProxy.Forward(msg)); Receive<BrowseNodeCommand>(msg => _deploymentManagerProxy.Forward(msg));
// Test Bindings (interactive design-time read) — same routing rationale // Test Bindings (interactive design-time read) — same routing rationale
// as BrowseOpcUaNodeCommand above: the singleton always lands on the // as BrowseNodeCommand above: the singleton always lands on the
// active site node, which is the node that owns the DataConnectionActor // active site node, which is the node that owns the DataConnectionActor
// children holding the live OPC UA sessions. // children holding the live OPC UA sessions.
Receive<ReadTagValuesCommand>(msg => _deploymentManagerProxy.Forward(msg)); Receive<ReadTagValuesCommand>(msg => _deploymentManagerProxy.Forward(msg));
@@ -360,13 +360,13 @@ public class CommunicationService
/// <param name="command">The OPC UA browse command.</param> /// <param name="command">The OPC UA browse command.</param>
/// <param name="cancellationToken">Cancellation token.</param> /// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The browse result (children + truncation flag + structured failure).</returns> /// <returns>The browse result (children + truncation flag + structured failure).</returns>
public Task<BrowseOpcUaNodeResult> BrowseOpcUaNodeAsync( public Task<BrowseNodeResult> BrowseNodeAsync(
string siteId, string siteId,
BrowseOpcUaNodeCommand command, BrowseNodeCommand command,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var envelope = new SiteEnvelope(siteId, command); var envelope = new SiteEnvelope(siteId, command);
return GetActor().Ask<BrowseOpcUaNodeResult>( return GetActor().Ask<BrowseNodeResult>(
envelope, _options.QueryTimeout, cancellationToken); envelope, _options.QueryTimeout, cancellationToken);
} }
@@ -377,7 +377,7 @@ public class CommunicationService
/// server backing the given data connection. Used by the CentralUI "Test /// server backing the given data connection. Used by the CentralUI "Test
/// Bindings" dialog on the Configure Instance page. The Ask is bounded by /// Bindings" dialog on the Configure Instance page. The Ask is bounded by
/// <see cref="CommunicationOptions.QueryTimeout"/> — same latency budget /// <see cref="CommunicationOptions.QueryTimeout"/> — same latency budget
/// as <see cref="BrowseOpcUaNodeAsync"/> (both are interactive one-shot /// as <see cref="BrowseNodeAsync"/> (both are interactive one-shot
/// design-time queries). /// design-time queries).
/// </summary> /// </summary>
/// <param name="siteId">The target site identifier.</param> /// <param name="siteId">The target site identifier.</param>
@@ -234,7 +234,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
// apply it so its state survives into the next ReSubscribeAll. // apply it so its state survives into the next ReSubscribeAll.
HandleSubscribeCompleted(sc); HandleSubscribeCompleted(sc);
break; break;
case BrowseOpcUaNodeCommand browse: case BrowseNodeCommand browse:
// Browse is an interactive design-time query; never stash. The // Browse is an interactive design-time query; never stash. The
// adapter has no session yet in this state, so reply with a // adapter has no session yet in this state, so reply with a
// typed ConnectionNotConnected failure so the dialog can render // typed ConnectionNotConnected failure so the dialog can render
@@ -307,7 +307,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
case RetryTagResolution: case RetryTagResolution:
HandleRetryTagResolution(); HandleRetryTagResolution();
break; break;
case BrowseOpcUaNodeCommand browse: case BrowseNodeCommand browse:
HandleBrowse(browse); HandleBrowse(browse);
break; break;
case ReadTagValuesCommand read: case ReadTagValuesCommand read:
@@ -432,7 +432,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
// apply it so its state survives into the next ReSubscribeAll. // apply it so its state survives into the next ReSubscribeAll.
HandleSubscribeCompleted(sc); HandleSubscribeCompleted(sc);
break; break;
case BrowseOpcUaNodeCommand browse: case BrowseNodeCommand browse:
// Browse is design-time and never stashed. While reconnecting // Browse is design-time and never stashed. While reconnecting
// the adapter has no live session, so the adapter call will // the adapter has no live session, so the adapter call will
// throw ConnectionNotConnectedException — mapped by HandleBrowse. // throw ConnectionNotConnectedException — mapped by HandleBrowse.
@@ -982,7 +982,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
// ── OPC UA Tag Browser (interactive design-time query) ── // ── OPC UA Tag Browser (interactive design-time query) ──
/// <summary> /// <summary>
/// Handles a <see cref="BrowseOpcUaNodeCommand"/> forwarded by the /// Handles a <see cref="BrowseNodeCommand"/> forwarded by the
/// <see cref="DataConnectionManagerActor"/>. The capability check (does /// <see cref="DataConnectionManagerActor"/>. The capability check (does
/// this adapter support browsing?) and all browse-failure mapping live /// this adapter support browsing?) and all browse-failure mapping live
/// here because the adapter is held by this actor, not the manager. /// here because the adapter is held by this actor, not the manager.
@@ -999,14 +999,14 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
/// <see cref="HandleWrite"/> — so the captured <see cref="Sender"/> is /// <see cref="HandleWrite"/> — so the captured <see cref="Sender"/> is
/// safe to use from the continuation (which runs off the actor thread). /// safe to use from the continuation (which runs off the actor thread).
/// </summary> /// </summary>
private void HandleBrowse(BrowseOpcUaNodeCommand command) private void HandleBrowse(BrowseNodeCommand command)
{ {
var sender = Sender; var sender = Sender;
if (_adapter is not IBrowsableDataConnection browsable) if (_adapter is not IBrowsableDataConnection browsable)
{ {
_log.Debug("[{0}] Browse requested but adapter does not implement IBrowsableDataConnection", _connectionName); _log.Debug("[{0}] Browse requested but adapter does not implement IBrowsableDataConnection", _connectionName);
sender.Tell(new BrowseOpcUaNodeResult( sender.Tell(new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure( new BrowseFailure(
@@ -1021,21 +1021,21 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
{ {
if (t.IsCompletedSuccessfully) if (t.IsCompletedSuccessfully)
{ {
return new BrowseOpcUaNodeResult(t.Result.Children, t.Result.Truncated, Failure: null); return new BrowseNodeResult(t.Result.Children, t.Result.Truncated, Failure: null);
} }
var baseEx = t.Exception?.GetBaseException(); var baseEx = t.Exception?.GetBaseException();
return baseEx switch return baseEx switch
{ {
ConnectionNotConnectedException notConnected => new BrowseOpcUaNodeResult( ConnectionNotConnectedException notConnected => new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure(BrowseFailureKind.ConnectionNotConnected, notConnected.Message)), new BrowseFailure(BrowseFailureKind.ConnectionNotConnected, notConnected.Message)),
OperationCanceledException => new BrowseOpcUaNodeResult( OperationCanceledException => new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure(BrowseFailureKind.Timeout, "Browse cancelled.")), new BrowseFailure(BrowseFailureKind.Timeout, "Browse cancelled.")),
_ => new BrowseOpcUaNodeResult( _ => new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure( new BrowseFailure(
@@ -46,7 +46,7 @@ public class DataConnectionManagerActor : ReceiveActor
Receive<WriteTagRequest>(HandleRouteWrite); Receive<WriteTagRequest>(HandleRouteWrite);
Receive<RemoveConnectionCommand>(HandleRemoveConnection); Receive<RemoveConnectionCommand>(HandleRemoveConnection);
Receive<GetAllHealthReports>(HandleGetAllHealthReports); Receive<GetAllHealthReports>(HandleGetAllHealthReports);
Receive<BrowseOpcUaNodeCommand>(HandleBrowse); Receive<BrowseNodeCommand>(HandleBrowse);
Receive<ReadTagValuesCommand>(HandleReadTagValues); Receive<ReadTagValuesCommand>(HandleReadTagValues);
} }
@@ -115,7 +115,7 @@ public class DataConnectionManagerActor : ReceiveActor
} }
/// <summary> /// <summary>
/// Routes a <see cref="BrowseOpcUaNodeCommand"/> from the central UI's OPC UA /// Routes a <see cref="BrowseNodeCommand"/> from the central UI's OPC UA
/// Tag Browser to the child <see cref="DataConnectionActor"/> that owns the /// Tag Browser to the child <see cref="DataConnectionActor"/> that owns the
/// named connection. The manager is the only actor that knows whether a /// named connection. The manager is the only actor that knows whether a
/// connection exists at this site — so it owns the /// connection exists at this site — so it owns the
@@ -123,7 +123,7 @@ public class DataConnectionManagerActor : ReceiveActor
/// else (capability check, session state, server errors) lives inside the /// else (capability check, session state, server errors) lives inside the
/// child where the adapter is held. /// child where the adapter is held.
/// </summary> /// </summary>
private void HandleBrowse(BrowseOpcUaNodeCommand command) private void HandleBrowse(BrowseNodeCommand command)
{ {
if (_connectionActors.TryGetValue(command.ConnectionName, out var actor)) if (_connectionActors.TryGetValue(command.ConnectionName, out var actor))
{ {
@@ -132,7 +132,7 @@ public class DataConnectionManagerActor : ReceiveActor
else else
{ {
_log.Warning("No connection actor for {0} during browse", command.ConnectionName); _log.Warning("No connection actor for {0} during browse", command.ConnectionName);
Sender.Tell(new BrowseOpcUaNodeResult( Sender.Tell(new BrowseNodeResult(
Array.Empty<BrowseNode>(), Array.Empty<BrowseNode>(),
Truncated: false, Truncated: false,
new BrowseFailure( new BrowseFailure(
@@ -149,12 +149,12 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
Receive<RouteToSetAttributesRequest>(RouteInboundApiSetAttributes); Receive<RouteToSetAttributesRequest>(RouteInboundApiSetAttributes);
// OPC UA Tag Browser — singleton-only re-forward to local /user/dcl-manager. // OPC UA Tag Browser — singleton-only re-forward to local /user/dcl-manager.
// BrowseOpcUaNodeCommand is routed to this singleton (active node) by // BrowseNodeCommand is routed to this singleton (active node) by
// SiteCommunicationActor so the dcl-manager we forward to is guaranteed // SiteCommunicationActor so the dcl-manager we forward to is guaranteed
// to be the one holding the live DataConnectionActor children. ActorSelection // to be the one holding the live DataConnectionActor children. ActorSelection
// has no Forward() extension in this Akka.NET version, so we Tell with the // has no Forward() extension in this Akka.NET version, so we Tell with the
// original Sender preserved (semantically identical to Forward). // original Sender preserved (semantically identical to Forward).
Receive<BrowseOpcUaNodeCommand>(msg => Receive<BrowseNodeCommand>(msg =>
Context.ActorSelection("/user/dcl-manager").Tell(msg, Sender)); Context.ActorSelection("/user/dcl-manager").Tell(msg, Sender));
// Test Bindings — same singleton-only re-forward as the browse handler // Test Bindings — same singleton-only re-forward as the browse handler
@@ -3,7 +3,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages; namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages;
/// <summary> /// <summary>
/// Verifies that <see cref="BrowseOpcUaNodeCommand"/> is discovered by /// Verifies that <see cref="BrowseNodeCommand"/> is discovered by
/// <see cref="ManagementCommandRegistry"/> so it travels over the management /// <see cref="ManagementCommandRegistry"/> so it travels over the management
/// boundary as a known command (resolvable by wire name and round-trippable /// boundary as a known command (resolvable by wire name and round-trippable
/// through <c>GetCommandName</c> / <c>Resolve</c>). /// through <c>GetCommandName</c> / <c>Resolve</c>).
@@ -11,13 +11,13 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages;
public class BrowseCommandsRegistryTests public class BrowseCommandsRegistryTests
{ {
[Fact] [Fact]
public void Registry_discovers_BrowseOpcUaNodeCommand() public void Registry_discovers_BrowseNodeCommand()
{ {
// GetCommandName throws ArgumentException for any type the registry // GetCommandName throws ArgumentException for any type the registry
// does not contain, so a successful call here is proof of discovery. // does not contain, so a successful call here is proof of discovery.
var name = ManagementCommandRegistry.GetCommandName(typeof(BrowseOpcUaNodeCommand)); var name = ManagementCommandRegistry.GetCommandName(typeof(BrowseNodeCommand));
Assert.Equal("BrowseOpcUaNode", name); Assert.Equal("BrowseNode", name);
Assert.Equal(typeof(BrowseOpcUaNodeCommand), ManagementCommandRegistry.Resolve(name)); Assert.Equal(typeof(BrowseNodeCommand), ManagementCommandRegistry.Resolve(name));
} }
} }
@@ -14,7 +14,7 @@ namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Actors;
/// Task 10 (opcua-tag-browser): the site-side /// Task 10 (opcua-tag-browser): the site-side
/// <see cref="DataConnectionManagerActor"/> + child /// <see cref="DataConnectionManagerActor"/> + child
/// <see cref="DataConnectionActor"/> together resolve /// <see cref="DataConnectionActor"/> together resolve
/// <see cref="BrowseOpcUaNodeCommand"/> against the live adapter and surface /// <see cref="BrowseNodeCommand"/> against the live adapter and surface
/// every browse outcome as a typed <see cref="BrowseFailure"/>. The split is: /// every browse outcome as a typed <see cref="BrowseFailure"/>. The split is:
/// the manager owns <see cref="BrowseFailureKind.ConnectionNotFound"/> (only it /// the manager owns <see cref="BrowseFailureKind.ConnectionNotFound"/> (only it
/// knows the per-site connection set); everything else lives in the child where /// knows the per-site connection set); everything else lives in the child where
@@ -50,9 +50,9 @@ public class DataConnectionManagerBrowseHandlerTests : TestKit
// No CreateConnectionCommand sent — the manager has zero children, so a // No CreateConnectionCommand sent — the manager has zero children, so a
// browse against any name must be rejected with ConnectionNotFound // browse against any name must be rejected with ConnectionNotFound
// (the manager is the only actor with site-level visibility). // (the manager is the only actor with site-level visibility).
manager.Tell(new BrowseOpcUaNodeCommand("unknown-connection", ParentNodeId: null)); manager.Tell(new BrowseNodeCommand("unknown-connection", ParentNodeId: null));
var reply = ExpectMsg<BrowseOpcUaNodeResult>(); var reply = ExpectMsg<BrowseNodeResult>();
Assert.NotNull(reply.Failure); Assert.NotNull(reply.Failure);
Assert.Equal(BrowseFailureKind.ConnectionNotFound, reply.Failure!.Kind); Assert.Equal(BrowseFailureKind.ConnectionNotFound, reply.Failure!.Kind);
Assert.Empty(reply.Children); Assert.Empty(reply.Children);
@@ -80,9 +80,9 @@ public class DataConnectionManagerBrowseHandlerTests : TestKit
() => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"), () => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"),
TimeSpan.FromSeconds(2)); TimeSpan.FromSeconds(2));
manager.Tell(new BrowseOpcUaNodeCommand("conn-bare", ParentNodeId: null)); manager.Tell(new BrowseNodeCommand("conn-bare", ParentNodeId: null));
var reply = ExpectMsg<BrowseOpcUaNodeResult>(TimeSpan.FromSeconds(3)); var reply = ExpectMsg<BrowseNodeResult>(TimeSpan.FromSeconds(3));
Assert.NotNull(reply.Failure); Assert.NotNull(reply.Failure);
Assert.Equal(BrowseFailureKind.NotBrowsable, reply.Failure!.Kind); Assert.Equal(BrowseFailureKind.NotBrowsable, reply.Failure!.Kind);
Assert.Empty(reply.Children); Assert.Empty(reply.Children);
@@ -120,9 +120,9 @@ public class DataConnectionManagerBrowseHandlerTests : TestKit
() => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"), () => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"),
TimeSpan.FromSeconds(2)); TimeSpan.FromSeconds(2));
manager.Tell(new BrowseOpcUaNodeCommand("conn-ok", ParentNodeId: null)); manager.Tell(new BrowseNodeCommand("conn-ok", ParentNodeId: null));
var reply = ExpectMsg<BrowseOpcUaNodeResult>(TimeSpan.FromSeconds(3)); var reply = ExpectMsg<BrowseNodeResult>(TimeSpan.FromSeconds(3));
Assert.Null(reply.Failure); Assert.Null(reply.Failure);
Assert.Equal(2, reply.Children.Count); Assert.Equal(2, reply.Children.Count);
Assert.Equal("ns=2;s=A", reply.Children[0].NodeId); Assert.Equal("ns=2;s=A", reply.Children[0].NodeId);
@@ -155,9 +155,9 @@ public class DataConnectionManagerBrowseHandlerTests : TestKit
() => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"), () => _factory.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "Create"),
TimeSpan.FromSeconds(2)); TimeSpan.FromSeconds(2));
manager.Tell(new BrowseOpcUaNodeCommand("conn-down", ParentNodeId: null)); manager.Tell(new BrowseNodeCommand("conn-down", ParentNodeId: null));
var reply = ExpectMsg<BrowseOpcUaNodeResult>(TimeSpan.FromSeconds(3)); var reply = ExpectMsg<BrowseNodeResult>(TimeSpan.FromSeconds(3));
Assert.NotNull(reply.Failure); Assert.NotNull(reply.Failure);
Assert.Equal(BrowseFailureKind.ConnectionNotConnected, reply.Failure!.Kind); Assert.Equal(BrowseFailureKind.ConnectionNotConnected, reply.Failure!.Kind);
Assert.Empty(reply.Children); Assert.Empty(reply.Children);