189 lines
7.5 KiB
C#
189 lines
7.5 KiB
C#
using Grpc.Core;
|
|
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Client.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for the <see cref="LazyBrowseNode"/> walker over the BrowseChildren RPC.
|
|
/// </summary>
|
|
public sealed class LazyBrowseNodeTests
|
|
{
|
|
/// <summary>
|
|
/// Verifies that calling BrowseAsync with no parent returns the root nodes
|
|
/// from the first BrowseChildren reply and surfaces the per-child has-children hint.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Browse_NoParent_ReturnsRoots()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(1, "Plant", isArea: true), BuildObject(2, "Other")],
|
|
childHasChildren: [true, false],
|
|
cacheSequence: 1));
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
IReadOnlyList<LazyBrowseNode> roots = await client.BrowseAsync();
|
|
|
|
Assert.Equal(2, roots.Count);
|
|
Assert.Equal("Plant", roots[0].Object.TagName);
|
|
Assert.True(roots[0].HasChildrenHint);
|
|
Assert.False(roots[0].IsExpanded);
|
|
Assert.Equal("Other", roots[1].Object.TagName);
|
|
Assert.False(roots[1].HasChildrenHint);
|
|
Assert.False(roots[1].IsExpanded);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that ExpandAsync populates Children and marks the node expanded after one RPC.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Expand_PopulatesChildrenAndMarksExpanded()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(1, "Plant", isArea: true)],
|
|
childHasChildren: [true],
|
|
cacheSequence: 1));
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(10, "Line1")],
|
|
childHasChildren: [false],
|
|
cacheSequence: 1));
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
IReadOnlyList<LazyBrowseNode> roots = await client.BrowseAsync();
|
|
await roots[0].ExpandAsync();
|
|
|
|
Assert.True(roots[0].IsExpanded);
|
|
Assert.Single(roots[0].Children);
|
|
Assert.Equal("Line1", roots[0].Children[0].Object.TagName);
|
|
Assert.Equal(2, transport.BrowseChildrenCalls.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that a second ExpandAsync call is a no-op and issues no additional RPC.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Expand_CalledTwice_NoSecondRpc()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(1, "Plant", isArea: true)],
|
|
childHasChildren: [true],
|
|
cacheSequence: 1));
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(10, "Line1")],
|
|
childHasChildren: [false],
|
|
cacheSequence: 1));
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
IReadOnlyList<LazyBrowseNode> roots = await client.BrowseAsync();
|
|
await roots[0].ExpandAsync();
|
|
await roots[0].ExpandAsync();
|
|
|
|
Assert.Equal(2, transport.BrowseChildrenCalls.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that an RPC failure (NotFound) during expand is wrapped in MxGatewayException.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Expand_UnknownParent_ThrowsMxGatewayException()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(1, "Plant", isArea: true)],
|
|
childHasChildren: [true],
|
|
cacheSequence: 1));
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
IReadOnlyList<LazyBrowseNode> roots = await client.BrowseAsync();
|
|
|
|
// Queue the failure for the upcoming ExpandAsync call so it consumes
|
|
// the exception on its first RPC rather than the BrowseAsync above.
|
|
transport.BrowseChildrenExceptions.Enqueue(
|
|
new MxGatewayException(
|
|
"Parent not found",
|
|
new RpcException(new Status(StatusCode.NotFound, "Parent not found"))));
|
|
|
|
await Assert.ThrowsAsync<MxGatewayException>(async () => await roots[0].ExpandAsync());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that ExpandAsync drains multi-page sibling replies and forwards the page token.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Expand_MultiPageSiblings_GathersAllPages()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
// Roots
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(7, "Plant", isArea: true)],
|
|
childHasChildren: [true],
|
|
cacheSequence: 1));
|
|
// First child page (2 children) with a next token
|
|
BrowseChildrenReply childPage1 = BuildReply(
|
|
children: [BuildObject(70, "ChildA"), BuildObject(71, "ChildB")],
|
|
childHasChildren: [false, false],
|
|
cacheSequence: 1);
|
|
childPage1.NextPageToken = "7:abc:2";
|
|
transport.BrowseChildrenReplies.Enqueue(childPage1);
|
|
// Second child page (1 child) with no next token
|
|
transport.BrowseChildrenReplies.Enqueue(BuildReply(
|
|
children: [BuildObject(72, "ChildC")],
|
|
childHasChildren: [false],
|
|
cacheSequence: 1));
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
IReadOnlyList<LazyBrowseNode> roots = await client.BrowseAsync();
|
|
await roots[0].ExpandAsync();
|
|
|
|
Assert.Equal(3, roots[0].Children.Count);
|
|
Assert.Equal(3, transport.BrowseChildrenCalls.Count);
|
|
Assert.Equal("7:abc:2", transport.BrowseChildrenCalls[2].Request.PageToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies that BrowseChildrenOptions filter fields are forwarded to the BrowseChildren request.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Browse_WithFilter_ForwardsToRequest()
|
|
{
|
|
FakeGalaxyRepositoryTransport transport = CreateTransport();
|
|
await using GalaxyRepositoryClient client = CreateClient(transport);
|
|
|
|
await client.BrowseAsync(new BrowseChildrenOptions
|
|
{
|
|
TagNameGlob = "Mixer*",
|
|
AlarmBearingOnly = true,
|
|
});
|
|
|
|
BrowseChildrenRequest request = Assert.Single(transport.BrowseChildrenCalls).Request;
|
|
Assert.Equal("Mixer*", request.TagNameGlob);
|
|
Assert.True(request.AlarmBearingOnly);
|
|
}
|
|
|
|
private static GalaxyObject BuildObject(int id, string tag, bool isArea = false)
|
|
=> new() { GobjectId = id, TagName = tag, BrowseName = tag, IsArea = isArea };
|
|
|
|
private static BrowseChildrenReply BuildReply(
|
|
IReadOnlyList<GalaxyObject> children,
|
|
IReadOnlyList<bool> childHasChildren,
|
|
ulong cacheSequence)
|
|
{
|
|
BrowseChildrenReply reply = new() { TotalChildCount = children.Count, CacheSequence = cacheSequence };
|
|
reply.Children.AddRange(children);
|
|
reply.ChildHasChildren.AddRange(childHasChildren);
|
|
return reply;
|
|
}
|
|
|
|
private static GalaxyRepositoryClient CreateClient(FakeGalaxyRepositoryTransport transport)
|
|
=> new(transport.Options, transport);
|
|
|
|
private static FakeGalaxyRepositoryTransport CreateTransport()
|
|
=> new(new MxGatewayClientOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:5000"),
|
|
ApiKey = "test-api-key",
|
|
});
|
|
}
|