/* ======================================================================== * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Threading.Tasks; using NUnit.Framework; using Opc.Ua.AI.Client; namespace Opc.Ua.AI.Tests { /// /// Tests for the lightweight AI sub-client types that each wrap one OPC UA /// type instance: , , /// , , /// , and . /// [TestFixture] [Category("AI")] [Category("Client")] public sealed class AISubClientTests { private static readonly NodeId s_nodeId = new(5000u, 3); [Test] public void DatasetConstructorWithNullClientThrows() { Assert.Throws( () => new AIDatasetClient((AIClient)null!, s_nodeId)); } [Test] public void DatasetConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AIDatasetClient(h.Client, NodeId.Null)); } [Test] public async Task DatasetReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.DatasetId, new NodeId(5001u, 3), "ds-1"); h.AddValueChild(s_nodeId, BrowseNames.Name, new NodeId(5002u, 3), "training"); h.AddValueChild( s_nodeId, BrowseNames.SourceKind, new NodeId(5003u, 3), (int)DatasetSourceEnum.Synthetic); h.AddValueChild(s_nodeId, BrowseNames.ArtifactUri, new NodeId(5004u, 3), "https://blob/ds1"); h.AddValueChild(s_nodeId, BrowseNames.ContentType, new NodeId(5005u, 3), "application/json"); h.AddValueChild(s_nodeId, BrowseNames.SizeBytes, new NodeId(5006u, 3), (ulong)4096); h.AddValueChild(s_nodeId, BrowseNames.SampleCount, new NodeId(5007u, 3), (uint)100); AIDatasetSnapshot snapshot = await h.Client.Dataset(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.DatasetId, Is.EqualTo("ds-1")); Assert.That(snapshot.Name, Is.EqualTo("training")); Assert.That(snapshot.SourceKind, Is.EqualTo(DatasetSourceEnum.Synthetic)); Assert.That(snapshot.ArtifactUri, Is.EqualTo("https://blob/ds1")); Assert.That(snapshot.ContentType, Is.EqualTo("application/json")); Assert.That(snapshot.SizeBytes, Is.EqualTo(4096)); Assert.That(snapshot.SampleCount, Is.EqualTo(100)); }); } [Test] public async Task DatasetReadAsyncWithMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AIDatasetSnapshot snapshot = await h.Client.Dataset(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.DatasetId, Is.Null); Assert.That(snapshot.Name, Is.Null); Assert.That(snapshot.SizeBytes, Is.Zero); Assert.That(snapshot.SampleCount, Is.Zero); }); } [Test] public void InferenceJobConstructorWithNullClientThrows() { Assert.Throws( () => new AIInferenceJobClient((AIClient)null!, s_nodeId)); } [Test] public void InferenceJobConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AIInferenceJobClient(h.Client, NodeId.Null)); } [Test] public async Task InferenceJobReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.JobId, new NodeId(5010u, 3), "job-42"); h.AddValueChild(s_nodeId, BrowseNames.Deployment, new NodeId(5011u, 3), new NodeId(100u, 3)); h.AddValueChild(s_nodeId, BrowseNames.ResponsePayload, new NodeId(5012u, 3), ByteString.From(new byte[] { 0xCA, 0xFE })); h.AddValueChild(s_nodeId, BrowseNames.ResponseContentType, new NodeId(5013u, 3), "text/plain"); h.AddValueChild(s_nodeId, BrowseNames.ModelUsed, new NodeId(5014u, 3), new NodeId(200u, 3)); h.AddValueChild( s_nodeId, BrowseNames.FinishReason, new NodeId(5015u, 3), (int)FinishReasonEnum.Error); AIInferenceJobSnapshot snapshot = await h.Client.InferenceJob(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.JobId, Is.EqualTo("job-42")); Assert.That(snapshot.ResponseContentType, Is.EqualTo("text/plain")); Assert.That(snapshot.ResponsePayload.Length, Is.EqualTo(2)); Assert.That(snapshot.FinishReason, Is.EqualTo(FinishReasonEnum.Error)); }); } [Test] public async Task InferenceJobReadAsyncMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AIInferenceJobSnapshot snapshot = await h.Client.InferenceJob(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.JobId, Is.Null); Assert.That(snapshot.DeploymentId, Is.EqualTo(NodeId.Null)); Assert.That(snapshot.ModelUsed, Is.EqualTo(NodeId.Null)); }); } [Test] public void EvaluationRunConstructorWithNullClientThrows() { Assert.Throws( () => new AIEvaluationRunClient((AIClient)null!, s_nodeId)); } [Test] public void EvaluationRunConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AIEvaluationRunClient(h.Client, NodeId.Null)); } [Test] public async Task EvaluationRunReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.RunId, new NodeId(5020u, 3), "run-7"); h.AddValueChild(s_nodeId, BrowseNames.EvaluatedModel, new NodeId(5021u, 3), new NodeId(300u, 3)); h.AddValueChild(s_nodeId, BrowseNames.Passed, new NodeId(5022u, 3), true); h.AddValueChild(s_nodeId, BrowseNames.ReportUri, new NodeId(5024u, 3), "https://reports/7"); AIEvaluationRunSnapshot snapshot = await h.Client.EvaluationRun(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.RunId, Is.EqualTo("run-7")); Assert.That(snapshot.Passed, Is.True); Assert.That(snapshot.ReportUri, Is.EqualTo("https://reports/7")); }); } [Test] public async Task EvaluationRunReadAsyncMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AIEvaluationRunSnapshot snapshot = await h.Client.EvaluationRun(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.RunId, Is.Null); Assert.That(snapshot.Passed, Is.False); Assert.That(snapshot.EvaluatedModelId, Is.EqualTo(NodeId.Null)); }); } [Test] public void LearningJobConstructorWithNullClientThrows() { Assert.Throws( () => new AILearningJobClient((AIClient)null!, s_nodeId)); } [Test] public void LearningJobConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AILearningJobClient(h.Client, NodeId.Null)); } [Test] public async Task LearningJobReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.JobId, new NodeId(5030u, 3), "learn-3"); h.AddValueChild( s_nodeId, BrowseNames.State, new NodeId(5031u, 3), (int)LearningJobStateEnum.Training); h.AddValueChild(s_nodeId, BrowseNames.Progress, new NodeId(5032u, 3), 0.75); h.AddValueChild(s_nodeId, BrowseNames.CandidateModel, new NodeId(5033u, 3), new NodeId(400u, 3)); h.AddValueChild(s_nodeId, BrowseNames.TargetDeployment, new NodeId(5034u, 3), new NodeId(500u, 3)); AILearningJobSnapshot snapshot = await h.Client.LearningJob(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.JobId, Is.EqualTo("learn-3")); Assert.That(snapshot.State, Is.EqualTo(LearningJobStateEnum.Training)); Assert.That(snapshot.Progress, Is.EqualTo(0.75)); }); } [Test] public async Task LearningJobReadAsyncMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AILearningJobSnapshot snapshot = await h.Client.LearningJob(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.JobId, Is.Null); Assert.That(snapshot.Progress, Is.Zero); Assert.That(snapshot.CandidateModelId, Is.EqualTo(NodeId.Null)); Assert.That(snapshot.TargetDeploymentId, Is.EqualTo(NodeId.Null)); }); } [Test] public void ModelSourceConstructorWithNullClientThrows() { Assert.Throws( () => new AIModelSourceClient((AIClient)null!, s_nodeId)); } [Test] public void ModelSourceConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AIModelSourceClient(h.Client, NodeId.Null)); } [Test] public async Task ModelSourceReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.SourceId, new NodeId(5040u, 3), "src-1"); h.AddValueChild(s_nodeId, BrowseNames.EndpointUri, new NodeId(5041u, 3), "https://model-store.example.com"); h.AddValueChild( s_nodeId, BrowseNames.ApiDialect, new NodeId(5042u, 3), (int)ApiDialectEnum.OpenInferenceProtocol); h.AddValueChild( s_nodeId, BrowseNames.AuthenticationKind, new NodeId(5043u, 3), (int)AuthenticationKindEnum.BearerToken); h.AddValueChild(s_nodeId, BrowseNames.CredentialReference, new NodeId(5044u, 3), "vault://key1"); AIModelSourceSnapshot snapshot = await h.Client.Source(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.SourceId, Is.EqualTo("src-1")); Assert.That(snapshot.EndpointUri, Is.EqualTo("https://model-store.example.com")); Assert.That(snapshot.ApiDialect, Is.EqualTo(ApiDialectEnum.OpenInferenceProtocol)); Assert.That(snapshot.AuthenticationKind, Is.EqualTo(AuthenticationKindEnum.BearerToken)); Assert.That(snapshot.CredentialReference, Is.EqualTo("vault://key1")); }); } [Test] public async Task ModelSourceReadAsyncMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AIModelSourceSnapshot snapshot = await h.Client.Source(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.SourceId, Is.Null); Assert.That(snapshot.EndpointUri, Is.Null); }); } [Test] public void InferenceTransferConstructorWithNullClientThrows() { Assert.Throws( () => new AIInferenceTransferClient((AIClient)null!, s_nodeId)); } [Test] public void InferenceTransferConstructorWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.Throws( () => new AIInferenceTransferClient(h.Client, NodeId.Null)); } [Test] public async Task InferenceTransferReadAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); h.AddValueChild(s_nodeId, BrowseNames.TransferId, new NodeId(5050u, 3), "xfer-1"); h.AddValueChild( s_nodeId, BrowseNames.State, new NodeId(5051u, 3), (int)TransferStateEnum.Completed); h.AddValueChild(s_nodeId, BrowseNames.BytesTransferred, new NodeId(5052u, 3), (ulong)2048); h.AddValueChild(s_nodeId, BrowseNames.ModelUsed, new NodeId(5053u, 3), new NodeId(600u, 3)); h.AddValueChild(s_nodeId, BrowseNames.ResponseContentType, new NodeId(5054u, 3), "application/octet-stream"); AITransferSnapshot snapshot = await h.Client.Transfer(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.TransferId, Is.EqualTo("xfer-1")); Assert.That(snapshot.State, Is.EqualTo(TransferStateEnum.Completed)); Assert.That(snapshot.BytesTransferred, Is.EqualTo(2048)); Assert.That(snapshot.ResponseContentType, Is.EqualTo("application/octet-stream")); }); } [Test] public async Task InferenceTransferReadAsyncMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); AITransferSnapshot snapshot = await h.Client.Transfer(s_nodeId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.TransferId, Is.Null); Assert.That(snapshot.BytesTransferred, Is.Zero); Assert.That(snapshot.ModelUsed, Is.EqualTo(NodeId.Null)); }); } [Test] public void ClientDatasetFactoryCreatesClient() { var h = new AISessionHarness(); AIDatasetClient ds = h.Client.Dataset(s_nodeId); Assert.That(ds.DatasetNodeId, Is.EqualTo(s_nodeId)); } [Test] public void ClientInferenceJobFactoryCreatesClient() { var h = new AISessionHarness(); AIInferenceJobClient j = h.Client.InferenceJob(s_nodeId); Assert.That(j.JobNodeId, Is.EqualTo(s_nodeId)); } [Test] public void ClientLearningJobFactoryCreatesClient() { var h = new AISessionHarness(); AILearningJobClient lj = h.Client.LearningJob(s_nodeId); Assert.That(lj.JobNodeId, Is.EqualTo(s_nodeId)); } [Test] public void ClientEvaluationRunFactoryCreatesClient() { var h = new AISessionHarness(); AIEvaluationRunClient er = h.Client.EvaluationRun(s_nodeId); Assert.That(er.RunNodeId, Is.EqualTo(s_nodeId)); } [Test] public void ClientSourceFactoryCreatesClient() { var h = new AISessionHarness(); AIModelSourceClient src = h.Client.Source(s_nodeId); Assert.That(src.SourceNodeId, Is.EqualTo(s_nodeId)); } [Test] public void ClientTransferFactoryCreatesClient() { var h = new AISessionHarness(); AIInferenceTransferClient t = h.Client.Transfer(s_nodeId); Assert.That(t.TransferNodeId, Is.EqualTo(s_nodeId)); } [Test] public async Task DiscoverDatasetsReturnsNodesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Datasets, new NodeId(6000u, 3)); h.AddBrowse(new NodeId(6000u, 3), [h.Ref(s_nodeId, "Dataset1", ObjectTypes.DatasetType)]); ArrayOf nodes = await h.Client.DiscoverDatasetsAsync().ConfigureAwait(false); Assert.That(nodes.Count, Is.EqualTo(1)); Assert.That(nodes[0], Is.EqualTo(s_nodeId)); } [Test] public async Task DiscoverLearningJobsReturnsNodesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.LearningJobs, new NodeId(6100u, 3)); h.AddBrowse(new NodeId(6100u, 3), [h.Ref(s_nodeId, "Job1", ObjectTypes.LearningJobType)]); ArrayOf nodes = await h.Client.DiscoverLearningJobsAsync().ConfigureAwait(false); Assert.That(nodes.Count, Is.EqualTo(1)); } [Test] public async Task DiscoverInferenceJobsReturnsNodesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Jobs, new NodeId(6200u, 3)); h.AddBrowse(new NodeId(6200u, 3), [h.Ref(s_nodeId, "InfJob1", ObjectTypes.InferenceJobType)]); ArrayOf nodes = await h.Client.DiscoverInferenceJobsAsync().ConfigureAwait(false); Assert.That(nodes.Count, Is.EqualTo(1)); } [Test] public async Task DiscoverEvaluationRunsReturnsNodesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Evaluations, new NodeId(6300u, 3)); h.AddBrowse(new NodeId(6300u, 3), [h.Ref(s_nodeId, "Run1", ObjectTypes.EvaluationRunType)]); ArrayOf nodes = await h.Client.DiscoverEvaluationRunsAsync().ConfigureAwait(false); Assert.That(nodes.Count, Is.EqualTo(1)); } [Test] public async Task DiscoverSourcesReturnsNodesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Sources, new NodeId(6400u, 3)); h.AddBrowse(new NodeId(6400u, 3), [h.Ref(s_nodeId, "Src1", ObjectTypes.ModelSourceType)]); ArrayOf nodes = await h.Client.DiscoverSourcesAsync().ConfigureAwait(false); Assert.That(nodes.Count, Is.EqualTo(1)); } [Test] public async Task EnumerateDatasetsYieldsEntriesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Datasets, new NodeId(7000u, 3)); h.AddBrowse(new NodeId(7000u, 3), [h.Ref(s_nodeId, "Ds1", ObjectTypes.DatasetType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.EnumerateDatasetsAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); Assert.That(entries[0].BrowseName.Name, Is.EqualTo("Ds1")); } [Test] public async Task EnumerateLearningJobsYieldsEntriesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.LearningJobs, new NodeId(7100u, 3)); h.AddBrowse(new NodeId(7100u, 3), [h.Ref(s_nodeId, "LJ1", ObjectTypes.LearningJobType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.EnumerateLearningJobsAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); } [Test] public async Task EnumerateInferenceJobsYieldsEntriesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Jobs, new NodeId(7200u, 3)); h.AddBrowse(new NodeId(7200u, 3), [h.Ref(s_nodeId, "IJ1", ObjectTypes.InferenceJobType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.EnumerateInferenceJobsAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); } [Test] public async Task EnumerateEvaluationRunsYieldsEntriesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Evaluations, new NodeId(7300u, 3)); h.AddBrowse(new NodeId(7300u, 3), [h.Ref(s_nodeId, "ER1", ObjectTypes.EvaluationRunType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.EnumerateEvaluationRunsAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); } [Test] public async Task EnumerateSourcesYieldsEntriesAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Sources, new NodeId(7400u, 3)); h.AddBrowse(new NodeId(7400u, 3), [h.Ref(s_nodeId, "S1", ObjectTypes.ModelSourceType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.EnumerateSourcesAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); } [Test] public void DatasetsFolderIdUsesWellKnownNode() { var h = new AISessionHarness(); Assert.That(h.Client.DatasetsFolderId, Is.Not.EqualTo(NodeId.Null)); } [Test] public void LearningJobsFolderIdUsesWellKnownNode() { var h = new AISessionHarness(); Assert.That(h.Client.LearningJobsFolderId, Is.Not.EqualTo(NodeId.Null)); } [Test] public async Task GetJobsFolderIdAsyncResolvesChildAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Jobs, new NodeId(8000u, 3)); NodeId folderId = await h.Client.GetJobsFolderIdAsync().ConfigureAwait(false); Assert.That(folderId, Is.EqualTo(new NodeId(8000u, 3))); } [Test] public async Task GetSourcesFolderIdAsyncResolvesChildAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Sources, new NodeId(8100u, 3)); NodeId folderId = await h.Client.GetSourcesFolderIdAsync().ConfigureAwait(false); Assert.That(folderId, Is.EqualTo(new NodeId(8100u, 3))); } [Test] public async Task GetEvaluationsFolderIdAsyncResolvesChildAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Evaluations, new NodeId(8200u, 3)); NodeId folderId = await h.Client.GetEvaluationsFolderIdAsync().ConfigureAwait(false); Assert.That(folderId, Is.EqualTo(new NodeId(8200u, 3))); } [Test] public async Task GetRegistriesFolderIdAsyncResolvesChildAsync() { var h = new AISessionHarness(); h.AddChild(h.AIRootId, BrowseNames.Registries, new NodeId(8300u, 3)); NodeId folderId = await h.Client.GetRegistriesFolderIdAsync().ConfigureAwait(false); Assert.That(folderId, Is.EqualTo(new NodeId(8300u, 3))); } [Test] public async Task ModelEnumerateResourcesYieldsMatchingObjectsAsync() { var h = new AISessionHarness(); var resourceId = new NodeId(9000u, 3); h.AddBrowse(h.ModelNodeId, [h.Ref(resourceId, "Weights", ObjectTypes.ModelResourceType)]); var entries = new List(); await foreach (AINodeEntry entry in h.Client.Model(h.ModelNodeId).EnumerateResourcesAsync()) { entries.Add(entry); } Assert.That(entries, Has.Count.EqualTo(1)); Assert.That(entries[0].BrowseName.Name, Is.EqualTo("Weights")); } [Test] public async Task ModelReadResourceAsyncReturnsSnapshotAsync() { var h = new AISessionHarness(); var resId = new NodeId(9100u, 3); h.AddValueChild(resId, BrowseNames.ArtifactUri, new NodeId(9101u, 3), "https://models/weights.bin"); h.AddValueChild(resId, BrowseNames.ContentType, new NodeId(9102u, 3), "application/octet-stream"); h.AddValueChild(resId, BrowseNames.SizeBytes, new NodeId(9103u, 3), (ulong)1048576); h.AddValueChild(resId, BrowseNames.Digest, new NodeId(9104u, 3), ByteString.From([0xAB, 0xCD])); h.AddValueChild(resId, BrowseNames.DigestAlgorithm, new NodeId(9105u, 3), "SHA-256"); AIModelResourceSnapshot snapshot = await h.Client.Model(h.ModelNodeId) .ReadResourceAsync(resId).ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.ArtifactUri, Is.EqualTo("https://models/weights.bin")); Assert.That(snapshot.ContentType, Is.EqualTo("application/octet-stream")); Assert.That(snapshot.SizeBytes, Is.EqualTo(1048576)); Assert.That(snapshot.DigestAlgorithm, Is.EqualTo("SHA-256")); }); } [Test] public async Task ModelOpenSourceAsyncReturnsNullWhenNoSourceReferenceAsync() { var h = new AISessionHarness(); AIModelSourceClient? source = await h.Client.Model(h.ModelNodeId) .OpenSourceAsync().ConfigureAwait(false); Assert.That(source, Is.Null); } [Test] public async Task ModelReadResourceAsyncWithMissingChildrenReturnsDefaultsAsync() { var h = new AISessionHarness(); var resId = new NodeId(9200u, 3); AIModelResourceSnapshot snapshot = await h.Client.Model(h.ModelNodeId) .ReadResourceAsync(resId).ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.ArtifactUri, Is.Null); Assert.That(snapshot.ContentType, Is.Null); Assert.That(snapshot.SizeBytes, Is.Zero); }); } [Test] public void ModelReadResourceAsyncWithNullNodeIdThrows() { var h = new AISessionHarness(); Assert.ThrowsAsync( async () => await h.Client.Model(h.ModelNodeId) .ReadResourceAsync(NodeId.Null).ConfigureAwait(false)); } [Test] public async Task ModelReadWithAllFieldsPopulatedReturnsCompleteSnapshotAsync() { var h = new AISessionHarness(); NodeId modelId = h.ModelNodeId; h.AddValueChild(modelId, BrowseNames.ModelId, new NodeId(9300u, 3), "m-full"); h.AddValueChild(modelId, BrowseNames.Name, new NodeId(9301u, 3), "FullModel"); h.AddValueChild(modelId, BrowseNames.Version, new NodeId(9302u, 3), "2.0"); h.AddValueChild(modelId, BrowseNames.Framework, new NodeId(9303u, 3), "ONNX"); h.AddValueChild(modelId, BrowseNames.Format, new NodeId(9304u, 3), "onnx"); h.AddValueChild(modelId, BrowseNames.License, new NodeId(9305u, 3), "MIT"); h.AddValueChild(modelId, BrowseNames.Digest, new NodeId(9306u, 3), ByteString.From([1])); h.AddValueChild(modelId, BrowseNames.DigestAlgorithm, new NodeId(9307u, 3), "SHA-256"); h.AddValueChild(modelId, BrowseNames.Publisher, new NodeId(9310u, 3), new NodeId(700u, 3)); AIModelSnapshot snapshot = await h.Client.Model(modelId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.ModelId, Is.EqualTo("m-full")); Assert.That(snapshot.Framework, Is.EqualTo("ONNX")); Assert.That(snapshot.Format, Is.EqualTo("onnx")); Assert.That(snapshot.License, Is.EqualTo("MIT")); Assert.That(snapshot.DigestAlgorithm, Is.EqualTo("SHA-256")); }); } [Test] public async Task DeploymentReadWithAllFieldsPopulatedReturnsCompleteSnapshotAsync() { var h = new AISessionHarness(); NodeId depId = h.DeploymentNodeId; h.AddValueChild(depId, BrowseNames.DeploymentId, new NodeId(9400u, 3), "dep-full"); h.AddValueChild( depId, BrowseNames.InferenceLocation, new NodeId(9401u, 3), (int)InferenceLocationEnum.EdgeOffServer); h.AddValueChild( depId, BrowseNames.State, new NodeId(9402u, 3), (int)DeploymentStateEnum.Active); h.AddValueChild(depId, BrowseNames.DataJurisdiction, new NodeId(9403u, 3), "US"); h.AddValueChild(depId, BrowseNames.EgressPermitted, new NodeId(9404u, 3), false); h.AddValueChild(depId, BrowseNames.MaxInlinePayloadSize, new NodeId(9405u, 3), (ulong)8192); h.AddValueChild(depId, BrowseNames.EndpointUri, new NodeId(9406u, 3), "https://ai.example.com/deploy"); AIDeploymentSnapshot snapshot = await h.Client.Deployment(depId).ReadAsync() .ConfigureAwait(false); Assert.Multiple(() => { Assert.That(snapshot.DeploymentId, Is.EqualTo("dep-full")); Assert.That(snapshot.InferenceLocation, Is.EqualTo(InferenceLocationEnum.EdgeOffServer)); Assert.That(snapshot.State, Is.EqualTo(DeploymentStateEnum.Active)); Assert.That(snapshot.DataJurisdiction, Is.EqualTo("US")); Assert.That(snapshot.EgressPermitted, Is.False); Assert.That(snapshot.MaxInlinePayloadSize, Is.EqualTo(8192)); Assert.That(snapshot.EndpointUri, Is.EqualTo("https://ai.example.com/deploy")); }); } } }