# OPC UA Client test fixture Coverage map + gap inventory for the OPC UA Client (gateway / aggregation) driver. **TL;DR:** Wire-level coverage now exists via [opc-plc](https://github.com/Azure-Samples/iot-edge-opc-plc) — Microsoft Industrial IoT's OPC UA PLC simulator running in Docker (task #215). Real Secure Channel, real Session, real MonitoredItem exchange against an independent server implementation. Unit tests still carry the exhaustive capability matrix (cert auth / security policies / reconnect / failover / attribute mapping). Gaps remaining: upstream-server-specific quirks (historian aggregates, typed ConditionType events, SDK-publish-queue edge behavior under load) — opc-plc uses the same OPCFoundation stack internally so fully-independent-stack coverage needs `open62541/open62541` as a second image (follow-up). ## What the fixture is **Integration layer** (task #215): `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/` stands up `mcr.microsoft.com/iotedge/opc-plc:2.14.10` via `Docker/docker-compose.yml` on `opc.tcp://10.100.0.35:50000` (the shared Docker host; override via `OPCUA_SIM_ENDPOINT`). `OpcPlcFixture` probes the port at collection init + skips tests with a clear message when the container's not running (matches the Modbus/pymodbus + S7/python-snap7 skip pattern). Docker is the launcher — no PowerShell wrapper needed because opc-plc ships pre-containerized. Compose-file flags: `--ut` (unsecured transport advertised), `--aa` (auto-accept client certs — opc-plc's cert trust store resets on each spin-up), `--alm` (alarm simulation for IAlarmSource follow-up coverage), `--pn=50000` (port). **Unit layer**: `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/` is still the primary coverage. Tests inject fakes through the driver's construction path; the OPCFoundation.NetStandard `Session` surface is wrapped behind an interface the tests mock. ## What it actually covers ### Integration (opc-plc Docker, task #215) - `OpcUaClientSmokeTests.Client_connects_and_reads_StepUp_node_through_real_OPC_UA_stack` — full Secure Channel + Session + `ns=3;s=StepUp` Read round-trip - `OpcUaClientSmokeTests.Client_reads_batch_of_varied_types_from_live_simulator` — batch Read of UInt32 / Int32 / Boolean; asserts `bool`-specific Variant decoding to catch a common attribute-mapping regression - `OpcUaClientSmokeTests.Client_subscribe_receives_StepUp_data_changes_from_live_server` — real `MonitoredItem` subscription against `ns=3;s=FastUInt1` (ticks every 100 ms); asserts `OnDataChange` fires within 3 s of subscribe Wire-level surfaces verified: `IDriver` + `IReadable` + `ISubscribable` + `IHostConnectivityProbe` (via the Secure Channel exchange). ### Unit The surface is broad because `OpcUaClientDriver` is the richest-capability driver in the fleet (it's a gateway for another OPC UA server, so it mirrors the full capability matrix): - `OpcUaClientDriverScaffoldTests` — `IDriver` lifecycle - `OpcUaClientReadWriteTests` — read + write lifecycle - `OpcUaClientSubscribeAndProbeTests` — monitored-item subscription + probe state transitions - `OpcUaClientDiscoveryTests` — `GetEndpoints` + endpoint selection - `OpcUaClientAttributeMappingTests` — OPC UA node attribute → driver value mapping - `OpcUaClientSecurityPolicyTests` — `SignAndEncrypt` / `Sign` / `None` policy negotiation contract - `OpcUaClientCertAuthTests` — cert store paths, revocation-list config - `OpcUaClientReconnectTests` — SDK reconnect hook + `TransferSubscriptions` across the disconnect boundary - `OpcUaClientFailoverTests` — primary → secondary session fallback per driver config - `OpcUaClientAlarmTests` — A&E severity bucket (1–1000 → Low / Medium / High / Critical), subscribe / unsubscribe / ack contract - `OpcUaClientHistoryTests` — historical data read + interpolation contract Capability surfaces whose contract is verified: `IDriver`, `ITagDiscovery`, `IReadable`, `IWritable`, `ISubscribable`, `IHostConnectivityProbe`, `IAlarmSource`, `IHistoryProvider`. ## What it does NOT cover ### 1. Full real-stack exchange (unit tests only) The **unit** suite mocks `Session.ReadAsync`, `Session.CreateSubscription`, `Session.AddItem`, etc. — no UA Secure Channel is opened. The **integration** suite (`OpcUaClientSmokeTests`, task #215) does open a real Secure Channel against opc-plc and exercises Read + Subscribe end-to-end. What remains untested even in the integration suite: certificate validation under non-anonymous security policies, signing/encryption, nonce handling, chunk assembly, keep-alive cadence — all SDK-internal. ### 2. Subscription transfer across reconnect Contract test: "after a simulated reconnect, `TransferSubscriptions` is called with the right handles." Real behavior: SDK re-publishes against the new channel and some events can be lost depending on publish-queue state. The lossy window is not characterized. ### 3. Large-scale subscription stress 100+ monitored items with heterogeneous publish intervals under a single session — the shape that breaks publish-queue-size tuning in the wild — is not exercised. ### 4. Real historian mappings `IHistoryProvider.ReadRawAsync` + `ReadProcessedAsync` + `ReadAtTimeAsync` + `ReadEventsAsync` are contract-mocked. Against a real historian (AVEVA Historian, Prosys historian, Kepware LocalHistorian) each has specific interpolation + bad-quality-handling quirks the contract test doesn't see. ### 5. Real A&E events Alarm subscription is mocked via filtered monitored items; the actual `EventFilter` select-clause behavior against a server that exposes typed ConditionType events (non-base `BaseEventType`) is not verified. ### 6. Authentication variants - Anonymous, UserName/Password, X509 cert tokens — each is contract-tested but not exchanged against a server that actually enforces each. - LDAP-backed `UserName` (matching this repo's server-side `LdapUserAuthenticator`) requires a live LDAP round-trip; not tested. ## When to trust OpcUaClient tests, when to reach for a server | Question | Unit tests | Integration (opc-plc) | Real upstream server | | --- | --- | --- | --- | | "Does severity 750 bucket as High?" | yes | - | yes | | "Does the driver call `TransferSubscriptions` after reconnect?" | yes | - | yes | | "Does a real OPC UA read round-trip work?" | no | yes | yes | | "Does a real OPC UA subscribe deliver changes?" | no | yes | yes | | "Does write round-trip work against a live server?" | no | no (not yet exercised) | yes (required) | | "Does event-filter-based alarm subscription return ConditionType events?" | no | no | yes (required) | | "Does history read from AVEVA Historian return correct aggregates?" | no | no | yes (required) | | "Does the SDK's publish queue lose notifications under load?" | no | no | yes (stress) | ## Follow-up candidates The easiest win here is to **wire the client driver tests against this repo's own server**. The v2 integration test project `tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs` (the v2 replacement for the retired v1 `OpcUaServerIntegrationTests`) already stands up a real OPC UA server on a non-default port with a seeded FakeDriver. An `OpcUaClientLiveLoopbackTests` that connects the client driver to that server would give: - Real Secure Channel negotiation - Real Session / Subscription / MonitoredItem exchange - Real read/write round-trip - Real certificate validation (the integration test already sets up PKI) It wouldn't cover upstream-server-specific quirks (AVEVA Historian, Kepware, Prosys), but it would cover 80% of the SDK surface the driver sits on top of. Beyond that: 1. **Prosys OPC UA Simulation Server** — free, Windows-available, scriptable. 2. **UaExpert Server-Side Simulator** — Unified Automation's sample server; good coverage of typed ConditionType events. 3. **Dedicated historian integration lab** — only path for historian-specific coverage. ## Key fixture / config files - `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/` — unit tests with mocked `Session` - `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcPlcFixture.cs` — collection fixture; parses `OPCUA_SIM_ENDPOINT` (default `opc.tcp://10.100.0.35:50000`), TCP-probes at collection init, records `SkipReason` when unreachable - `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientSmokeTests.cs` — wire-level test suite (3 `[Fact]` methods: read, batch read, subscribe) - `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/docker-compose.yml` — `mcr.microsoft.com/iotedge/opc-plc:2.14.10` with `--ut --aa --alm --pn=50000` - `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs` — ctor + session-factory seam tests mock through; implements `IAlarmSource` + `IHistoryProvider` (unique among drivers); does NOT implement `IRediscoverable` - `tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/DualEndpointTests.cs` — the v2 dual-endpoint integration harness a future loopback client test could piggyback on (v1 `OpcUaServerIntegrationTests.cs` retired with the v1 server project)