Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions src/Opc.Ua.Client/AliasNames/AliasNameClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.Client;

namespace Opc.Ua.Client.AliasNames
{
Expand Down Expand Up @@ -444,13 +445,7 @@ public async IAsyncEnumerable<AliasNameSubCategoryInfo>
throw new ServiceResultException(br.StatusCode);
}

int refCount = br.References.Count;
var refs = new ReferenceDescription[refCount];
for (int i = 0; i < refCount; i++)
{
refs[i] = br.References[i];
}
foreach (ReferenceDescription r in refs)
foreach (ReferenceDescription r in SnapshotReferences(br.References))
{
if (!r.TypeDefinition.Equals(ObjectTypeIds.AliasNameCategoryType))
{
Expand All @@ -467,12 +462,53 @@ public async IAsyncEnumerable<AliasNameSubCategoryInfo>
r.BrowseName,
r.DisplayName);
}

ByteString continuationPoint = br.ContinuationPoint;
while (!continuationPoint.IsEmpty)
{
(_, continuationPoint, ArrayOf<ReferenceDescription> nextReferences) =
await Session.BrowseNextAsync(
requestHeader: null,
releaseContinuationPoint: false,
continuationPoint,
ct).ConfigureAwait(false);

foreach (ReferenceDescription r in SnapshotReferences(nextReferences))
{
if (!r.TypeDefinition.Equals(ObjectTypeIds.AliasNameCategoryType))
{
continue;
}
var localId = ExpandedNodeId.ToNodeId(
r.NodeId, Session.NamespaceUris);
if (localId.IsNull)
{
continue;
}
yield return new AliasNameSubCategoryInfo(
localId,
r.BrowseName,
r.DisplayName);
}
}
}

// --------------------------------------------------------------
// Internal helpers
// --------------------------------------------------------------

private static ReferenceDescription[] SnapshotReferences(
ArrayOf<ReferenceDescription> references)
{
int refCount = references.Count;
var snapshot = new ReferenceDescription[refCount];
for (int i = 0; i < refCount; i++)
{
snapshot[i] = references[i];
}
return snapshot;
}

private async Task<NodeId> ResolveChildAsync(
string childBrowseName,
CancellationToken ct)
Expand Down
61 changes: 61 additions & 0 deletions tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,5 +228,66 @@
]).ConfigureAwait(false);
}, Throws.TypeOf<System.UnauthorizedAccessException>());
}

[Test]
public async Task EnumerateSubCategoriesAsyncFollowsBrowseContinuationPointsAsync()
{
var harness = AliasNameSessionHarness.Create();
var continuationPoint = new ByteString([0x01]);

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-windows-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

Check failure on line 236 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

Cannot initialize type 'ReadOnlyMemory<byte>' with a collection expression because the type is not constructible.

harness.BrowseHandler = description =>
{
Assert.That(description.NodeId, Is.EqualTo(ObjectIds.Aliases));
return new BrowseResult
{
StatusCode = StatusCodes.Good,
ContinuationPoint = continuationPoint,
References = new[]
{
CreateCategoryReference("Page1", "Page1")
}.ToArrayOf()
};
};

harness.BrowseNextHandler = cp =>
{
Assert.That(cp, Is.EqualTo(continuationPoint));
return new BrowseResult
{
StatusCode = StatusCodes.Good,
ContinuationPoint = ByteString.Empty,
References = new[]
{
CreateCategoryReference("Page2", "Page2")
}.ToArrayOf()
};
};

var client = AliasNameClient.OpenStandardAliases(harness.Session);
var names = new List<string>();

await foreach (AliasNameSubCategoryInfo info in
client.EnumerateSubCategoriesAsync().ConfigureAwait(false))
{
names.Add(info.BrowseName.Name);
}

Assert.That(names, Is.EqualTo(new[] { "Page1", "Page2" }));

Check warning on line 275 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)

Check warning on line 275 in tests/Opc.Ua.Client.Tests/AliasNames/AliasNameClientTests.cs

View workflow job for this annotation

GitHub Actions / build-linux-all-tfm

Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861)
Assert.That(harness.BrowseRequests, Has.Count.EqualTo(1));
Assert.That(harness.BrowseNextRequests, Has.Count.EqualTo(1));

static ReferenceDescription CreateCategoryReference(
string browseName,
string nodeId)
{
return new ReferenceDescription
{
NodeId = new ExpandedNodeId(new NodeId(nodeId, 2)),
BrowseName = new QualifiedName(browseName, 2),
DisplayName = new LocalizedText(browseName),
TypeDefinition = ObjectTypeIds.AliasNameCategoryType
};
}
}
}
}
51 changes: 44 additions & 7 deletions tests/Opc.Ua.Client.Tests/AliasNames/AliasNameSessionHarness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ internal sealed class AliasNameSessionHarness
public IServiceMessageContext MessageContext { get; }
public List<CallMethodRequest> CallRequests { get; } = [];
public List<ReadValueId> ReadRequests { get; } = [];
public List<BrowseDescription> BrowseRequests { get; } = [];
public List<ByteString> BrowseNextRequests { get; } = [];
public Func<CallMethodRequest, CallMethodResult> CallHandler { get; set; }
public Func<ReadValueId, DataValue> ReadHandler { get; set; }
public Func<BrowseDescription, BrowseResult> BrowseHandler { get; set; }
public Func<ByteString, BrowseResult> BrowseNextHandler { get; set; }

public Func<BrowsePath, BrowsePathResult> BrowsePathHandler { get; set; }

Expand Down Expand Up @@ -164,13 +168,11 @@ public static AliasNameSessionHarness Create()
var results = new BrowseResult[descriptions.Count];
for (int i = 0; i < descriptions.Count; i++)
{
results[i] = new BrowseResult
{
StatusCode = StatusCodes.Good,
References = Array
.Empty<ReferenceDescription>()
.ToArrayOf()
};
BrowseDescription description = descriptions[i];
harness.BrowseRequests.Add(description);
results[i] = harness.BrowseHandler != null
? harness.BrowseHandler(description)
: DefaultBrowseResult();
}
return new ValueTask<BrowseResponse>(new BrowseResponse
{
Expand All @@ -180,6 +182,32 @@ public static AliasNameSessionHarness Create()
});
});

sessionMock
.Setup(s => s.BrowseNextAsync(
It.IsAny<RequestHeader>(),
It.IsAny<bool>(),
It.IsAny<ArrayOf<ByteString>>(),
It.IsAny<CancellationToken>()))
.Returns<RequestHeader, bool, ArrayOf<ByteString>, CancellationToken>(
(_, _, continuationPoints, _) =>
{
var results = new BrowseResult[continuationPoints.Count];
for (int i = 0; i < continuationPoints.Count; i++)
{
ByteString continuationPoint = continuationPoints[i];
harness.BrowseNextRequests.Add(continuationPoint);
results[i] = harness.BrowseNextHandler != null
? harness.BrowseNextHandler(continuationPoint)
: DefaultBrowseResult();
}
return new ValueTask<BrowseNextResponse>(new BrowseNextResponse
{
ResponseHeader = new ResponseHeader(),
Results = results.ToArrayOf(),
DiagnosticInfos = default
});
});

return harness;
}

Expand All @@ -192,6 +220,15 @@ private static CallMethodResult DefaultCallResult()
};
}

private static BrowseResult DefaultBrowseResult()
{
return new BrowseResult
{
StatusCode = StatusCodes.Good,
References = Array.Empty<ReferenceDescription>().ToArrayOf()
};
}

/// <summary>
/// Default browse-path resolver — returns a synthetic NodeId
/// (browseName-named string identifier in ns=0) so that
Expand Down
Loading