test(gateway): reconcile Galaxy tests to the shared library (delete upstream-owned, rebind host-specific)
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.Abstractions.ApiKeys;
|
||||
using ZB.MOM.WW.GalaxyRepository;
|
||||
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
||||
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Metrics;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
||||
@@ -215,6 +216,35 @@ public sealed class DashboardSnapshotServiceTests
|
||||
[Fact]
|
||||
public void GetSnapshot_ProjectsGalaxySummaryFromHierarchyCache()
|
||||
{
|
||||
// The shared-library cache entry no longer carries a precomputed dashboard summary;
|
||||
// DashboardSnapshotService derives templates/categories from the entry's objects via
|
||||
// DashboardGalaxyProjector. Seed objects that yield $Pump x2 / $Area x1 templates and
|
||||
// categories UserDefined(10) x2 / Area(13) x1, matching the asserted summary.
|
||||
GalaxyObject[] objects =
|
||||
[
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 1,
|
||||
BrowseName = "AreaA",
|
||||
IsArea = true,
|
||||
CategoryId = 13,
|
||||
TemplateChain = { "$Area" },
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 2,
|
||||
BrowseName = "Pump01",
|
||||
CategoryId = 10,
|
||||
TemplateChain = { "$Pump" },
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 3,
|
||||
BrowseName = "Pump02",
|
||||
CategoryId = 10,
|
||||
TemplateChain = { "$Pump" },
|
||||
},
|
||||
];
|
||||
GalaxyHierarchyCacheEntry entry = GalaxyHierarchyCacheEntry.Empty with
|
||||
{
|
||||
Status = GalaxyCacheStatus.Healthy,
|
||||
@@ -222,27 +252,8 @@ public sealed class DashboardSnapshotServiceTests
|
||||
LastQueriedAt = DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
|
||||
LastSuccessAt = DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
|
||||
LastDeployTime = DateTimeOffset.Parse("2026-04-28T09:00:00Z", CultureInfo.InvariantCulture),
|
||||
DashboardSummary = new DashboardGalaxySummary(
|
||||
DashboardGalaxyStatus.Healthy,
|
||||
LastQueriedAt: DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
|
||||
LastSuccessAt: DateTimeOffset.Parse("2026-04-28T11:30:00Z", CultureInfo.InvariantCulture),
|
||||
LastDeployTime: DateTimeOffset.Parse("2026-04-28T09:00:00Z", CultureInfo.InvariantCulture),
|
||||
LastError: null,
|
||||
ObjectCount: 3,
|
||||
AreaCount: 1,
|
||||
AttributeCount: 2,
|
||||
HistorizedAttributeCount: 1,
|
||||
AlarmAttributeCount: 1,
|
||||
TopTemplates:
|
||||
[
|
||||
new DashboardGalaxyTemplateUsage("$Pump", 2),
|
||||
new DashboardGalaxyTemplateUsage("$Area", 1),
|
||||
],
|
||||
ObjectCategories:
|
||||
[
|
||||
new DashboardGalaxyCategoryCount(10, "UserDefined", 2),
|
||||
new DashboardGalaxyCategoryCount(13, "Area", 1),
|
||||
]),
|
||||
Objects = objects,
|
||||
Index = GalaxyHierarchyIndex.Build(objects),
|
||||
ObjectCount = 3,
|
||||
AreaCount = 1,
|
||||
AttributeCount = 2,
|
||||
|
||||
@@ -1,486 +0,0 @@
|
||||
using Grpc.Core;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Grpc;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
||||
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
||||
using ZB.MOM.WW.MxGateway.Tests.TestSupport;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Grpc;
|
||||
|
||||
public sealed class GalaxyRepositoryGrpcServiceTests
|
||||
{
|
||||
/// <summary>Verifies that DiscoverHierarchy returns the requested page and totals.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_ReturnsRequestedPageAndTotals()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateObjects(3)));
|
||||
|
||||
DiscoverHierarchyReply reply = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = 2,
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Equal(2, reply.Objects.Count);
|
||||
Assert.Equal("Object_001", reply.Objects[0].TagName);
|
||||
Assert.Equal("Object_002", reply.Objects[1].TagName);
|
||||
Assert.StartsWith("7:", reply.NextPageToken, StringComparison.Ordinal);
|
||||
Assert.EndsWith(":2", reply.NextPageToken, StringComparison.Ordinal);
|
||||
Assert.Equal(3, reply.TotalObjectCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with a page token returns remaining objects.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithNextPageToken_ReturnsRemainingObjects()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateObjects(3)));
|
||||
DiscoverHierarchyReply firstPage = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = 2,
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
DiscoverHierarchyReply reply = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = 2,
|
||||
PageToken = firstPage.NextPageToken,
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
GalaxyObject item = Assert.Single(reply.Objects);
|
||||
Assert.Equal("Object_003", item.TagName);
|
||||
Assert.Equal("", reply.NextPageToken);
|
||||
Assert.Equal(3, reply.TotalObjectCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with invalid paging arguments returns InvalidArgument.</summary>
|
||||
/// <param name="pageToken">The page token to test.</param>
|
||||
/// <param name="pageSize">The page size to test.</param>
|
||||
[Theory]
|
||||
[InlineData("-1", 1)]
|
||||
[InlineData("not-an-offset", 1)]
|
||||
[InlineData("7:4", 1)]
|
||||
[InlineData("6:2", 1)]
|
||||
[InlineData("", -1)]
|
||||
public async Task DiscoverHierarchy_WithInvalidPagingArguments_ReturnsInvalidArgument(
|
||||
string pageToken,
|
||||
int pageSize)
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateObjects(3)));
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = pageSize,
|
||||
PageToken = pageToken,
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with subtree root and depth filters descendants.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithSubtreeRootAndDepth_FiltersDescendants()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
|
||||
DiscoverHierarchyReply reply = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
RootContainedPath = "Area1/Line3",
|
||||
MaxDepth = 1,
|
||||
PageSize = 10,
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Equal(["Line3", "Pump_001", "Valve_001"], reply.Objects.Select(obj => obj.TagName));
|
||||
Assert.Equal(3, reply.TotalObjectCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy applies server-side filters and omits attributes.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithServerSideFilters_AppliesAllFiltersAndOmitsAttributes()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
|
||||
DiscoverHierarchyReply reply = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
RootTagName = "Area1",
|
||||
TagNameGlob = "Pump_*",
|
||||
AlarmBearingOnly = true,
|
||||
HistorizedOnly = true,
|
||||
IncludeAttributes = false,
|
||||
PageSize = 10,
|
||||
CategoryIds = { 10 },
|
||||
TemplateChainContains = { "Pump" },
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
GalaxyObject obj = Assert.Single(reply.Objects);
|
||||
Assert.Equal("Pump_001", obj.TagName);
|
||||
Assert.Empty(obj.Attributes);
|
||||
Assert.Equal(1, reply.TotalObjectCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with filtered paging returns post-filter total.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithFilteredPaging_ReturnsPostFilterTotal()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
|
||||
DiscoverHierarchyReply first = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
RootGobjectId = 1,
|
||||
PageSize = 1,
|
||||
CategoryIds = { 10 },
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
DiscoverHierarchyReply second = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
RootGobjectId = 1,
|
||||
PageSize = 1,
|
||||
PageToken = first.NextPageToken,
|
||||
CategoryIds = { 10 },
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
GalaxyObject firstObject = Assert.Single(first.Objects);
|
||||
GalaxyObject secondObject = Assert.Single(second.Objects);
|
||||
Assert.Equal(2, first.TotalObjectCount);
|
||||
Assert.Equal(2, second.TotalObjectCount);
|
||||
Assert.NotEqual(firstObject.TagName, secondObject.TagName);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with mismatched filter token returns InvalidArgument.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithMismatchedFilterToken_ReturnsInvalidArgument()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
DiscoverHierarchyReply first = await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = 1,
|
||||
CategoryIds = { 10 },
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
PageSize = 1,
|
||||
PageToken = first.NextPageToken,
|
||||
CategoryIds = { 11 },
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
Assert.Contains("filters", exception.Status.Detail, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DiscoverHierarchy with missing root returns NotFound.</summary>
|
||||
[Fact]
|
||||
public async Task DiscoverHierarchy_WithMissingRoot_ReturnsNotFound()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.DiscoverHierarchy(
|
||||
new DiscoverHierarchyRequest
|
||||
{
|
||||
RootTagName = "Missing",
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.NotFound, exception.StatusCode);
|
||||
}
|
||||
|
||||
private static GalaxyRepositoryGrpcService CreateService(GalaxyHierarchyCacheEntry entry)
|
||||
{
|
||||
GalaxyRepositoryOptions options = new()
|
||||
{
|
||||
ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;Encrypt=False;",
|
||||
};
|
||||
return new GalaxyRepositoryGrpcService(
|
||||
new global::ZB.MOM.WW.MxGateway.Server.Galaxy.GalaxyRepository(options),
|
||||
new StubGalaxyHierarchyCache(entry),
|
||||
new GalaxyDeployNotifier(),
|
||||
new GatewayRequestIdentityAccessor());
|
||||
}
|
||||
|
||||
private static GalaxyHierarchyCacheEntry CreateEntry(IReadOnlyList<GalaxyObject> objects)
|
||||
{
|
||||
return GalaxyHierarchyCacheEntry.Empty with
|
||||
{
|
||||
Status = GalaxyCacheStatus.Healthy,
|
||||
Sequence = 7,
|
||||
LastSuccessAt = DateTimeOffset.UtcNow,
|
||||
Objects = objects,
|
||||
Index = GalaxyHierarchyIndex.Build(objects),
|
||||
DashboardSummary = DashboardGalaxySummary.Unknown with
|
||||
{
|
||||
Status = DashboardGalaxyStatus.Healthy,
|
||||
ObjectCount = objects.Count,
|
||||
},
|
||||
ObjectCount = objects.Count,
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<GalaxyObject> CreateObjects(int count)
|
||||
{
|
||||
return Enumerable.Range(1, count)
|
||||
.Select(index => new GalaxyObject
|
||||
{
|
||||
GobjectId = index,
|
||||
TagName = $"Object_{index:000}",
|
||||
BrowseName = $"Object_{index:000}",
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static IReadOnlyList<GalaxyObject> CreateFilterObjects()
|
||||
{
|
||||
return
|
||||
[
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 1,
|
||||
TagName = "Area1",
|
||||
ContainedName = "Area1",
|
||||
BrowseName = "Area1",
|
||||
IsArea = true,
|
||||
CategoryId = 13,
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 2,
|
||||
TagName = "Line3",
|
||||
ContainedName = "Line3",
|
||||
BrowseName = "Line3",
|
||||
ParentGobjectId = 1,
|
||||
CategoryId = 10,
|
||||
TemplateChain = { "$Line", "$Base" },
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 3,
|
||||
TagName = "Pump_001",
|
||||
ContainedName = "Pump",
|
||||
BrowseName = "Pump_001",
|
||||
ParentGobjectId = 2,
|
||||
CategoryId = 10,
|
||||
TemplateChain = { "$Pump", "$Base" },
|
||||
Attributes =
|
||||
{
|
||||
new GalaxyAttribute
|
||||
{
|
||||
AttributeName = "PV",
|
||||
FullTagReference = "Pump_001.PV",
|
||||
IsAlarm = true,
|
||||
IsHistorized = true,
|
||||
SecurityClassification = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 4,
|
||||
TagName = "Valve_001",
|
||||
ContainedName = "Valve",
|
||||
BrowseName = "Valve_001",
|
||||
ParentGobjectId = 2,
|
||||
CategoryId = 11,
|
||||
TemplateChain = { "$Valve" },
|
||||
Attributes =
|
||||
{
|
||||
new GalaxyAttribute
|
||||
{
|
||||
AttributeName = "PV",
|
||||
FullTagReference = "Valve_001.PV",
|
||||
},
|
||||
},
|
||||
},
|
||||
new GalaxyObject
|
||||
{
|
||||
GobjectId = 5,
|
||||
TagName = "Other_001",
|
||||
ContainedName = "Other",
|
||||
BrowseName = "Other_001",
|
||||
CategoryId = 10,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
private sealed class StubGalaxyHierarchyCache(GalaxyHierarchyCacheEntry current) : IGalaxyHierarchyCache
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public GalaxyHierarchyCacheEntry Current { get; } = current;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RefreshAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that BrowseChildren returns root objects and the current cache sequence when called with no parent.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseChildren_RootCall_ReturnsRootsWithCacheSequence()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
|
||||
BrowseChildrenReply reply = await service.BrowseChildren(
|
||||
new BrowseChildrenRequest(),
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Equal(2, reply.Children.Count);
|
||||
Assert.Equal("Area1", reply.Children[0].TagName);
|
||||
Assert.Equal("Other_001", reply.Children[1].TagName);
|
||||
Assert.Equal(7UL, reply.CacheSequence);
|
||||
Assert.Equal(2, reply.TotalChildCount);
|
||||
Assert.Equal(reply.Children.Count, reply.ChildHasChildren.Count);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that BrowseChildren returns Unavailable when the cache's first load never completes.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseChildren_FirstLoadNotComplete_ReturnsUnavailable()
|
||||
{
|
||||
GalaxyRepositoryOptions options = new()
|
||||
{
|
||||
ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;Encrypt=False;",
|
||||
};
|
||||
GalaxyRepositoryGrpcService service = new(
|
||||
new global::ZB.MOM.WW.MxGateway.Server.Galaxy.GalaxyRepository(options),
|
||||
new NeverLoadsHierarchyCache(),
|
||||
new GalaxyDeployNotifier(),
|
||||
new GatewayRequestIdentityAccessor());
|
||||
|
||||
// No caller-supplied CT so WaitForCacheBootstrap exits via its 5s internal budget
|
||||
// (instead of re-throwing OperationCanceledException from the caller's CT). The
|
||||
// handler then sees Status=Unknown and returns Unavailable.
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.BrowseChildren(
|
||||
new BrowseChildrenRequest(),
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.Unavailable, exception.StatusCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a page token bound to a stale cache sequence is rejected with InvalidArgument.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseChildren_StaleToken_ReturnsInvalidArgument()
|
||||
{
|
||||
GalaxyRepositoryGrpcService firstService = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
BrowseChildrenReply firstReply = await firstService.BrowseChildren(
|
||||
new BrowseChildrenRequest { PageSize = 1 },
|
||||
new TestServerCallContext());
|
||||
Assert.False(string.IsNullOrEmpty(firstReply.NextPageToken));
|
||||
|
||||
GalaxyHierarchyCacheEntry newerEntry = CreateEntry(CreateFilterObjects()) with { Sequence = 8 };
|
||||
GalaxyRepositoryGrpcService secondService = CreateService(newerEntry);
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await secondService.BrowseChildren(
|
||||
new BrowseChildrenRequest
|
||||
{
|
||||
PageSize = 1,
|
||||
PageToken = firstReply.NextPageToken,
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
Assert.Contains("stale", exception.Status.Detail, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that switching filters between paged BrowseChildren calls is rejected.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseChildren_FilterChangeBetweenPages_ReturnsInvalidArgument()
|
||||
{
|
||||
GalaxyRepositoryGrpcService service = CreateService(CreateEntry(CreateFilterObjects()));
|
||||
BrowseChildrenReply firstReply = await service.BrowseChildren(
|
||||
new BrowseChildrenRequest
|
||||
{
|
||||
ParentGobjectId = 2,
|
||||
PageSize = 1,
|
||||
},
|
||||
new TestServerCallContext());
|
||||
Assert.False(string.IsNullOrEmpty(firstReply.NextPageToken));
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.BrowseChildren(
|
||||
new BrowseChildrenRequest
|
||||
{
|
||||
ParentGobjectId = 2,
|
||||
PageSize = 1,
|
||||
PageToken = firstReply.NextPageToken,
|
||||
TagNameGlob = "Pump*",
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
Assert.Contains("filters", exception.Status.Detail, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an ApiKeyIdentity browse-subtrees constraint that matches nothing produces an empty child list.</summary>
|
||||
[Fact]
|
||||
public async Task BrowseChildren_BrowseSubtreesConstraint_FiltersChildren()
|
||||
{
|
||||
GalaxyRepositoryOptions options = new()
|
||||
{
|
||||
ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;Encrypt=False;",
|
||||
};
|
||||
GatewayRequestIdentityAccessor identityAccessor = new();
|
||||
GalaxyRepositoryGrpcService service = new(
|
||||
new global::ZB.MOM.WW.MxGateway.Server.Galaxy.GalaxyRepository(options),
|
||||
new StubGalaxyHierarchyCache(CreateEntry(CreateFilterObjects())),
|
||||
new GalaxyDeployNotifier(),
|
||||
identityAccessor);
|
||||
|
||||
// Sanity: with no identity pushed, both Pump and Valve come back under Line3 (id=2).
|
||||
BrowseChildrenReply unconstrained = await service.BrowseChildren(
|
||||
new BrowseChildrenRequest { ParentGobjectId = 2 },
|
||||
new TestServerCallContext());
|
||||
Assert.Equal(2, unconstrained.Children.Count);
|
||||
|
||||
ApiKeyIdentity identity = new(
|
||||
KeyId: "test-key",
|
||||
KeyPrefix: "mxgw_test",
|
||||
DisplayName: "constraint-only",
|
||||
Scopes: new HashSet<string>(StringComparer.Ordinal) { GatewayScopes.MetadataRead },
|
||||
Constraints: ApiKeyConstraints.Empty with { BrowseSubtrees = new[] { "NonExistent" } });
|
||||
|
||||
using (identityAccessor.Push(identity))
|
||||
{
|
||||
BrowseChildrenReply constrained = await service.BrowseChildren(
|
||||
new BrowseChildrenRequest { ParentGobjectId = 2 },
|
||||
new TestServerCallContext());
|
||||
Assert.Empty(constrained.Children);
|
||||
Assert.Equal(0, constrained.TotalChildCount);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NeverLoadsHierarchyCache : IGalaxyHierarchyCache
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public GalaxyHierarchyCacheEntry Current { get; } =
|
||||
GalaxyHierarchyCacheEntry.Empty with { Status = GalaxyCacheStatus.Unknown };
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RefreshAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) =>
|
||||
Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
||||
using ZB.MOM.WW.GalaxyRepository;
|
||||
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
||||
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
||||
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Sessions;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using ZB.MOM.WW.GalaxyRepository;
|
||||
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
||||
using ZB.MOM.WW.MxGateway.Server.Sessions;
|
||||
using ZB.MOM.WW.MxGateway.Server.Workers;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user