[WotCon] Add the WoT Connectivity registry client - #4142
Conversation
Adds the client-side surface for the WoT Connectivity registry: browsing and reading registry groups, resources and versions, uploading documents through FileTransfer, and invoking the registry methods. WotRegistryClient derives from the shared XRegistryClient so the generic registry traversal is not reimplemented here, and it takes an explicit registry root so a server that hosts the registry somewhere other than the well-known location can still be used. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Reverts the temporary client-only compile exclusions so the WotCon test project keeps the existing server, provider and hosting tests from the base branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
…into marcschier/wot-15-client
…into marcschier/wot-15-client
|
Follow-up: the initial PR 15 commit temporarily excluded the non-client WotCon tests from Validation now passes:
|
|
Confirmed after merging the fixed Validation with the full test project intact:
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds the WoT Connectivity 1.1 registry client surface (plus supporting xRegistry and core type enhancements) and wires it into DI/builder helpers, including comprehensive session-mock tests.
Changes:
- Introduce
WotRegistryClient+ group/resource wrappers, plus a shared browse-path resolver and DI registration helpers. - Extend xRegistry client construction to accept an explicit registry root
NodeId. - Add atomic file replacement to the
IFileSystemabstraction and update built-in file system implementations + tests; enhance Variant/ExtensionObject structure decoding and related type-system tests.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/Opc.Ua.SourceGeneration/SourceGeneratorFileSystem.cs | Implements new IFileSystem.Replace API as not supported for the source generator FS. |
| tests/Opc.Ua.XRegistry.Tests/XRegistryClientTests.cs | Adds tests for explicit registry root NodeId behavior in XRegistryClient. |
| tests/Opc.Ua.WotCon.Tests/Hosting/OpcUaWotRegistryClientBuilderExtensionsTests.cs | Adds DI/builder tests for AddWotRegistryClient registration and behavior. |
| tests/Opc.Ua.WotCon.Tests/Client/WotRegistrySessionMock.cs | Adds an in-memory scripted ISession mock to test WoT registry behavior end-to-end. |
| tests/Opc.Ua.WotCon.Tests/Client/WotRegistryClientTests.cs | Adds WoT registry client tests (browse resolution, uploads, refresh, lifecycle). |
| tests/Opc.Ua.WotCon.Tests/Client/WotRegistryBulkLoadTests.cs | Adds tests for bulk document load ordering and failure propagation. |
| tests/Opc.Ua.WotCon.Tests/Client/WotConnectivityClientTests.cs | Updates/extends tests for connectivity client behavior (browse-path helper usage). |
| tests/Opc.Ua.Types.Tests/Utils/FileSystem/AtomicFileReplaceTests.cs | Adds tests validating atomic replace semantics across file systems. |
| tests/Opc.Ua.Types.Tests/Nodes/TypeTableTests.cs | Adds coverage for type/encoding equivalence handling. |
| tests/Opc.Ua.Types.Tests/BuiltIn/VariantCoverageTests.cs | Adds coverage for new Variant.TryGetStructure(context, ...) overloads. |
| tests/Opc.Ua.Types.Tests/BuiltIn/ExtensionObjectTests.cs | Adds coverage for context-based decoding of binary/xml/json extension bodies. |
| src/Opc.Ua.XRegistry.Client/XRegistryClient.cs | Adds explicit-root constructor and stores RegistryNodeId as an instance property. |
| src/Opc.Ua.XRegistry.Client/GenericXRegistryClient.cs | Adds explicit-root constructor for generic registry client. |
| src/Opc.Ua.WotCon.Client/WotRegistryResourceClient.cs | Adds resource wrapper with validate/enable/default/delete and upload/download helpers. |
| src/Opc.Ua.WotCon.Client/WotRegistryGroupClient.cs | Adds group wrapper for create/get-or-create/open resource and delete operations. |
| src/Opc.Ua.WotCon.Client/WotRegistryClient.cs | Adds primary WoT registry client (group ops, refresh, bulk load workflow). |
| src/Opc.Ua.WotCon.Client/WotConnectivityClient.cs | Refactors browse-path resolution to shared resolver; minor doc formatting. |
| src/Opc.Ua.WotCon.Client/WotConBrowsePathResolver.cs | Adds centralized TranslateBrowsePaths helper for resolving single children. |
| src/Opc.Ua.WotCon.Client/Opc.Ua.WotCon.Client.csproj | Adds dependency on Opc.Ua.XRegistry.Client for shared base lifecycle. |
| src/Opc.Ua.WotCon.Client/NugetREADME.md | Updates package README to document registry client surface. |
| src/Opc.Ua.WotCon.Client/Hosting/WotRegistryClientOptions.cs | Adds registry-client DI options (LazyConnect). |
| src/Opc.Ua.WotCon.Client/Hosting/OpcUaWotConClientBuilderExtensions.cs | Adds AddWotRegistryClient extension methods and lazy accessor implementation. |
| src/Opc.Ua.Types/Utils/FileSystem/VirtualFileSystem.cs | Implements atomic Replace for in-memory virtual file system. |
| src/Opc.Ua.Types/Utils/FileSystem/ResourceFileSystem.cs | Implements Replace as not writeable for resource FS. |
| src/Opc.Ua.Types/Utils/FileSystem/NullFileSystem.cs | Implements Replace as not available for null FS. |
| src/Opc.Ua.Types/Utils/FileSystem/LocalFileSystem.cs | Implements Replace using File.Move(overwrite) or File.Replace fallback. |
| src/Opc.Ua.Types/Utils/FileSystem/IFileSystem.cs | Adds Replace(source, destination) API with atomic publish semantics. |
| src/Opc.Ua.Types/Utils/FileSystem/CombinedFileSystem.cs | Delegates Replace to the configured writeable side. |
| src/Opc.Ua.Types/Nodes/TypeTable.cs | Adjusts type/encoding equivalence logic and fixes doc indentation. |
| src/Opc.Ua.Types/BuiltIn/Variant.cs | Adds TryGetStructure(context, ...) overloads; adds docs for internal Union. |
| src/Opc.Ua.Types/BuiltIn/TypeInfo.cs | Improves data type id resolution for known extension object type ids. |
| src/Opc.Ua.Types/BuiltIn/ExtensionObject.cs | Adds context-based decoding for binary/xml/json bodies in TryGetValue. |
Suppressed comments (1)
src/Opc.Ua.Types/Utils/FileSystem/IFileSystem.cs:1
- The XML doc contains a duplicated
<param name="destinationPath">entry, which can break doc generation and IntelliSense. Remove the duplicate param line. Also, since some platform implementations (e.g.,File.Replaceon older TFMs) can fail across volumes/filesystems, it would be helpful to document any such constraints explicitly in this contract comment.
/* ========================================================================
The fluent builder generator emits calls to INodeManagerBuilder.VariableFromDataTypeId and to a NodeManagerBuilder constructor overload that carries the data-type lookup, but the runtime side of that API was missing, so every generated node manager failed to compile with CS1729 and CS1061. Adds the VariableFromDataTypeId resolution to the builder interface and implementation, along with NodeStateLookupExtensions.FindByDataType, which is the lookup the builder delegates to and has no other consumer. Resolution reports BadNodeIdInvalid for a null data type, BadNodeIdUnknown when nothing matches, BadBrowseNameDuplicated when the match is ambiguous, and BadTypeMismatch when the resolved node is not a variable. An optional browse name disambiguates a data type that is carried by more than one variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
|
Fixed the net10.0 solution build failure on this PR. This branch carries the The fix lands in #4132 (where the generator change belongs) and is cherry-picked here so Verified locally: |
DiNodeManager constructed NodeManagerBuilder without the data-type resolver, so
VariableFromDataTypeId reported BadNodeIdUnknown ("no predefined variable has DataType")
for every DI node manager - a misleading error, since the lookup had simply never
been supplied rather than the variable being absent.
Delegates to NodeStateLookupExtensions.FindByDataType rather than hand-rolling the
scan a fourth time in this file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
…client # Conflicts: # src/Opc.Ua.Di.Server/DiNodeManager.cs
Allow lazy client connection retries after failed attempts while keeping concurrent callers on the same in-flight task. Validate malformed method-call responses before indexing result arrays, avoid redundant resource lookups during bulk load, and add bounded spinning to virtual file replacement contention paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (69.25%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## marcschier/wot-10-wotcon-model #4142 +/- ##
==================================================================
+ Coverage 79.88% 80.13% +0.24%
==================================================================
Files 1540 1544 +4
Lines 214958 215517 +559
Branches 37133 37236 +103
==================================================================
+ Hits 171723 172701 +978
+ Misses 30301 29886 -415
+ Partials 12934 12930 -4
🚀 New features to boost your workflow:
|
The registry client's fault handling, result validation and dependency-injection configuration branches carried no tests, which is where a misbehaving or unreachable registry would actually be observable to a client application. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Patch coverage: 69.25 % → ~98 % (local measurement)
Local patch coverage (Cobertura from
|
| Before | After | |
|---|---|---|
| Changed lines hit | 570 / 712 | 700 / 712 |
| Patch coverage | 80.06 % | 98.31 % |
Per file:
| File | Before | After |
|---|---|---|
src/Opc.Ua.WotCon.Client/WotRegistryClient.cs |
64 missed / 293 (78.2 %) | 2 missed (99.3 %) |
src/Opc.Ua.WotCon.Client/Hosting/OpcUaWotConClientBuilderExtensions.cs |
30 missed / 94 (68.1 %) | 5 missed (94.7 %) |
src/Opc.Ua.WotCon.Client/WotRegistryResourceClient.cs |
21 missed / 75 (72.0 %) | 2 missed (97.3 %) |
src/Opc.Ua.WotCon.Client/WotRegistryGroupClient.cs |
11 missed / 54 (79.6 %) | 0 missed (100 %) |
src/Opc.Ua.WotCon.Client/WotConBrowsePathResolver.cs |
0 missed / 28 (no direct test) | 0 missed (100 %, now directly tested) |
src/Opc.Ua.XRegistry.Client/XRegistryClient.cs |
11 / 11 (100 %) | unchanged |
src/Opc.Ua.Types/BuiltIn/ExtensionObject.cs |
13 missed / 77 (83.1 %) | 1 missed (98.7 %) |
src/Opc.Ua.Types/Utils/FileSystem/LocalFileSystem.cs |
1 missed / 5 (80 %) | 0 missed (100 %) |
What the new tests cover
WotRegistryClientTests.cs (+24) — argument validation (ISession / registryObjectId / telemetry null, blank groupId / resourceId), Refresh fault handling (bad StatusCode from the server, too few output arguments, non-structure output arguments, summary ExtensionObject with no body, malformed binary body, undecodable per-resource result), the binary-ExtensionObject decoding fallback used when the server publishes its own encoding ids, group TypeDefinition resolution failures (server reports no HasTypeDefinition reference / an unrecognised ObjectType), WotRegistryRefreshResult.HasFailures + EnsureSuccess(), CreateResource / OpenResource / group Delete, and a Stream upload/download round trip.
WotConBrowsePathResolverTests.cs (new, +4) — the shared TranslateBrowsePaths helper: the happy path (asserting the emitted BrowsePath shape) plus the three degenerate responses a non-conformant server can return (no result, no target, bad result status), each of which must surface the caller-supplied status code and message.
OpcUaWotRegistryClientBuilderExtensionsTests.cs (+8) — AddWotRegistryClient on both IOpcUaBuilder and IOpcUaClientBuilder: IConfiguration / IConfigurationSection binding (verifying the options actually bind from OpcUa:WotCon:RegistryClient), all remaining null-argument guards, the accessor rejecting ConnectAsync after DisposeAsync (including the idempotent second dispose), and the lazy-connect factory when the registered ManagedSession factory yields no session.
WotRegistryBulkLoadTests.cs (+6) — LoadDocumentsAsync(default) null guard, all three WotRegistryDocument constructor guards plus its VersionId defaulting, and WotRegistryDocumentLoadOutcome.ResourceNodeId / .Created across a re-load.
ExtensionObjectTests.cs (+4) — the decode-failure catch filter (truncated binary body of a registered type) and the unknown-type early-outs for XML and JSON bodies, plus a not-well-formed XML body.
AtomicFileReplaceTests.cs (+1) — LocalFileSystem.Replace creating a missing destination directory.
WotRegistrySessionMock gained four scripting knobs (ReturnNoTypeDefinitionOnce, TypeDefinitionOverride, OverrideMethod, ResolveMethodId) so the existing mock stays the single mocking approach.
Remaining 12 uncovered changed lines — all deliberate
OpcUaWotConClientBuilderExtensions.cs556-562 — reachable only with a realManagedSession; its only constructor isprivate, so it cannot be constructed or mocked in a unit test.WotRegistryClient.cs357-358 andWotRegistryResourceClient.cs166-167 — unreachable.Variant.TryGetStructure(context, out T)already performs the identicalTryGetValue(out ExtensionObject) && extension.TryGetValue(out value, context)fallback, so the secondifbody in the client can never execute. The conditions themselves are covered. (Flagging rather than "fixing": removing the redundant branch is a source change and this PR is under review.)VirtualFileSystem.cs185-186 — theTryAddrace retry (spinner.SpinOnce(); continue;); only reachable under a lost concurrent publish, which cannot be forced deterministically.ExtensionObject.cs433 — needs a registered encodeable exposing a nullTypeId/BinaryEncodingId/XmlEncodingId; none exists.
Validation
| Project | net10.0 | net48 |
|---|---|---|
Opc.Ua.WotCon.Tests |
443 passed, 0 failed (was 401) | 443 passed, 0 failed |
Opc.Ua.XRegistry.Tests |
171 passed, 0 failed | — |
Opc.Ua.Types.Tests |
8430 passed, 0 failed (was 8425) | — |
All builds Release, 0 warnings / 0 errors with TreatWarningsAsErrors. dotnet format reports no changes on every touched file. No production code was modified.
ab49cd9 to
4c32ce3
Compare
# Conflicts: # src/Opc.Ua.Types/Utils/FileSystem/VirtualFileSystem.cs
4c32ce3 to
237c7d0
Compare
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
… marcschier/wot-15-client
PR 15 of a stacked split of integration PR #4093 into independently reviewable pieces.
This branch is stacked on #4139 (
marcschier/wot-10-wotcon-model) and additionally has #4128 (marcschier/wot-01-core-types) and #4130 (marcschier/wot-09-xregistry-client) merged in so this client slice can build.Those extra dependencies are intentional:
WotRegistryClient.cs:83—error CS1729: 'XRegistryClient' does not contain a constructor that takes 4 arguments.WotRegistryClient.cs:346,375andWotRegistryResourceClient.cs:144—error CS1501: No overload for method 'TryGetStructure' takes 2 arguments.The extra diff from #4128 and #4130 disappears once those PRs land.
This PR adds the WoT Connectivity registry client surface for browsing and reading registry groups, resources and versions, uploading documents through FileTransfer, invoking registry methods, and registering the client through DI/builder helpers.