459 lines
20 KiB
C#
459 lines
20 KiB
C#
using Grpc.Core;
|
|
using ZB.MOM.WW.GalaxyRepository;
|
|
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
|
|
|
namespace ZB.MOM.WW.GalaxyRepository.Tests;
|
|
|
|
/// <summary>
|
|
/// Pure-logic tests for <see cref="GalaxyHierarchyProjector"/> and
|
|
/// <see cref="GalaxyBrowseProjector"/>. No SQL: the cache entry under test is built
|
|
/// from a small hand-made hierarchy through the same materialization the live cache
|
|
/// uses (a fake <see cref="IGalaxyRepository"/> driven through
|
|
/// <see cref="GalaxyHierarchyCache.RefreshAsync"/>), so the projectors are exercised
|
|
/// against a real <see cref="GalaxyHierarchyIndex"/>.
|
|
/// </summary>
|
|
public sealed class GalaxyHierarchyProjectorTests
|
|
{
|
|
/// <summary>
|
|
/// Builds a realistic cache entry by driving a fake repository through the cache's
|
|
/// own refresh path. This goes through <c>BuildEntry</c> + <see cref="GalaxyHierarchyIndex.Build"/>
|
|
/// exactly as production does, rather than reaching for an internal factory.
|
|
/// </summary>
|
|
private static GalaxyHierarchyCacheEntry BuildEntry(
|
|
IReadOnlyList<GalaxyHierarchyRow> hierarchy,
|
|
IReadOnlyList<GalaxyAttributeRow> attributes)
|
|
{
|
|
FakeGalaxyRepository repository = new(hierarchy, attributes, deployTime: new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
using GalaxyHierarchyCache cache = new(repository, new RecordingDeployNotifier());
|
|
cache.RefreshAsync(CancellationToken.None).GetAwaiter().GetResult();
|
|
GalaxyHierarchyCacheEntry entry = cache.Current;
|
|
Assert.True(entry.HasData);
|
|
return entry;
|
|
}
|
|
|
|
// A small but representative galaxy:
|
|
// PlantArea (area, id 1)
|
|
// ├─ LineA (area, id 2)
|
|
// │ ├─ Pump01 (id 10, template "Pump", historized+alarm attr)
|
|
// │ └─ Valve01 (id 11, template "Valve", plain attr)
|
|
// └─ Mixer01 (id 12, template "Mixer", alarm attr only)
|
|
// StandaloneTank (id 20, no parent — a root object)
|
|
private static GalaxyHierarchyCacheEntry BuildSampleEntry()
|
|
{
|
|
List<GalaxyHierarchyRow> hierarchy =
|
|
[
|
|
Hierarchy(1, "PlantArea", parent: 0, isArea: true, category: 100),
|
|
Hierarchy(2, "LineA", parent: 1, isArea: true, category: 100),
|
|
Hierarchy(10, "Pump01", parent: 2, category: 200, templates: ["$Pump", "$UserDefined"]),
|
|
Hierarchy(11, "Valve01", parent: 2, category: 201, templates: ["$Valve"]),
|
|
Hierarchy(12, "Mixer01", parent: 1, category: 202, templates: ["$Mixer"]),
|
|
Hierarchy(20, "StandaloneTank", parent: 0, category: 203, templates: ["$Tank"]),
|
|
];
|
|
|
|
List<GalaxyAttributeRow> attributes =
|
|
[
|
|
// Pump01: historized AND alarm-bearing.
|
|
Attribute(10, "Pump01.PV", historized: true, alarm: true),
|
|
Attribute(10, "Pump01.SP", historized: false, alarm: false),
|
|
// Valve01: plain.
|
|
Attribute(11, "Valve01.Cmd", historized: false, alarm: false),
|
|
// Mixer01: alarm only.
|
|
Attribute(12, "Mixer01.Fault", historized: false, alarm: true),
|
|
// StandaloneTank: historized only.
|
|
Attribute(20, "StandaloneTank.Level", historized: true, alarm: false),
|
|
];
|
|
|
|
return BuildEntry(hierarchy, attributes);
|
|
}
|
|
|
|
private static GalaxyHierarchyRow Hierarchy(
|
|
int id,
|
|
string tagName,
|
|
int parent,
|
|
bool isArea = false,
|
|
int category = 0,
|
|
IReadOnlyList<string>? templates = null) => new()
|
|
{
|
|
GobjectId = id,
|
|
TagName = tagName,
|
|
ContainedName = tagName,
|
|
BrowseName = tagName,
|
|
ParentGobjectId = parent,
|
|
IsArea = isArea,
|
|
CategoryId = category,
|
|
TemplateChain = templates ?? Array.Empty<string>(),
|
|
};
|
|
|
|
private static GalaxyAttributeRow Attribute(
|
|
int gobjectId,
|
|
string fullTagReference,
|
|
bool historized,
|
|
bool alarm) => new()
|
|
{
|
|
GobjectId = gobjectId,
|
|
AttributeName = fullTagReference.Split('.')[^1],
|
|
FullTagReference = fullTagReference,
|
|
IsHistorized = historized,
|
|
IsAlarm = alarm,
|
|
};
|
|
|
|
[Fact]
|
|
public void Project_NoFilters_ReturnsEveryObject()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest());
|
|
|
|
Assert.Equal(6, result.TotalObjectCount);
|
|
Assert.Equal(6, result.Objects.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_PageSizeAndOffset_SlicesTheOrderedResult()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new();
|
|
|
|
GalaxyHierarchyQueryResult full = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: int.MaxValue);
|
|
GalaxyHierarchyQueryResult page1 = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 2);
|
|
GalaxyHierarchyQueryResult page2 = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 2, pageSize: 2);
|
|
GalaxyHierarchyQueryResult page3 = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 4, pageSize: 2);
|
|
|
|
// Total is unaffected by paging.
|
|
Assert.Equal(6, page1.TotalObjectCount);
|
|
Assert.Equal(2, page1.Objects.Count);
|
|
Assert.Equal(2, page2.Objects.Count);
|
|
Assert.Equal(2, page3.Objects.Count);
|
|
|
|
// The three pages reconstruct the full ordered result with no gaps/dupes.
|
|
List<int> paged =
|
|
[
|
|
.. page1.Objects.Select(o => o.GobjectId),
|
|
.. page2.Objects.Select(o => o.GobjectId),
|
|
.. page3.Objects.Select(o => o.GobjectId),
|
|
];
|
|
Assert.Equal(full.Objects.Select(o => o.GobjectId), paged);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_OffsetPastEnd_ReturnsEmptyPageButRealTotal()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(
|
|
entry, new DiscoverHierarchyRequest(), browseSubtreeGlobs: null, offset: 999, pageSize: 10);
|
|
|
|
Assert.Empty(result.Objects);
|
|
Assert.Equal(6, result.TotalObjectCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_PageSignature_IsStableAcrossPagesAndMatchesComputeFilterSignature()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { TagNameGlob = "Pump*" };
|
|
|
|
string expected = GalaxyHierarchyProjector.ComputeFilterSignature(request, browseSubtreeGlobs: null);
|
|
GalaxyHierarchyQueryResult page1 = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 1);
|
|
GalaxyHierarchyQueryResult page2 = GalaxyHierarchyProjector.Project(entry, request, browseSubtreeGlobs: null, offset: 1, pageSize: 1);
|
|
|
|
// The signature a caller computes to mint a page token round-trips: the projector
|
|
// reports the same signature on every page of the same filter set.
|
|
Assert.Equal(expected, page1.FilterSignature);
|
|
Assert.Equal(expected, page2.FilterSignature);
|
|
}
|
|
|
|
[Fact]
|
|
public void ComputeFilterSignature_DiffersWhenAnyFilterChanges()
|
|
{
|
|
DiscoverHierarchyRequest baseRequest = new() { TagNameGlob = "Pump*" };
|
|
DiscoverHierarchyRequest differentGlob = new() { TagNameGlob = "Valve*" };
|
|
DiscoverHierarchyRequest differentAlarm = new() { TagNameGlob = "Pump*", AlarmBearingOnly = true };
|
|
|
|
string baseSig = GalaxyHierarchyProjector.ComputeFilterSignature(baseRequest, null);
|
|
|
|
Assert.NotEqual(baseSig, GalaxyHierarchyProjector.ComputeFilterSignature(differentGlob, null));
|
|
Assert.NotEqual(baseSig, GalaxyHierarchyProjector.ComputeFilterSignature(differentAlarm, null));
|
|
Assert.NotEqual(baseSig, GalaxyHierarchyProjector.ComputeFilterSignature(baseRequest, browseSubtreeGlobs: ["PlantArea/*"]));
|
|
// Same inputs => same signature (deterministic).
|
|
Assert.Equal(baseSig, GalaxyHierarchyProjector.ComputeFilterSignature(new DiscoverHierarchyRequest { TagNameGlob = "Pump*" }, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_MaxDepthZero_FromRoot_ReturnsOnlyTheRoot()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { RootGobjectId = 1, MaxDepth = 0 };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
GalaxyObject only = Assert.Single(result.Objects);
|
|
Assert.Equal(1, only.GobjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_MaxDepthOne_FromRoot_ReturnsRootAndDirectChildrenOnly()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
// PlantArea(1) depth 0; LineA(2) and Mixer01(12) depth 1; Pump01/Valve01 depth 2.
|
|
DiscoverHierarchyRequest request = new() { RootGobjectId = 1, MaxDepth = 1 };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
Assert.Equal([1, 2, 12], result.Objects.Select(o => o.GobjectId).OrderBy(id => id));
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_NegativeMaxDepth_ThrowsInvalidArgument()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { MaxDepth = -1 };
|
|
|
|
RpcException ex = Assert.Throws<RpcException>(() => GalaxyHierarchyProjector.Project(entry, request));
|
|
Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_UnknownRoot_ThrowsNotFound()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { RootGobjectId = 99999 };
|
|
|
|
RpcException ex = Assert.Throws<RpcException>(() => GalaxyHierarchyProjector.Project(entry, request));
|
|
Assert.Equal(StatusCode.NotFound, ex.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_HistorizedOnly_ReturnsOnlyObjectsWithAHistorizedAttribute()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { HistorizedOnly = true };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
// Pump01(10) and StandaloneTank(20) carry historized attributes.
|
|
Assert.Equal([10, 20], result.Objects.Select(o => o.GobjectId).OrderBy(id => id));
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_AlarmBearingOnly_ReturnsOnlyObjectsWithAnAlarmAttribute()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { AlarmBearingOnly = true };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
// Pump01(10) and Mixer01(12) carry alarm attributes.
|
|
Assert.Equal([10, 12], result.Objects.Select(o => o.GobjectId).OrderBy(id => id));
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_AlarmAndHistorizedTogether_RequiresBoth()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { AlarmBearingOnly = true, HistorizedOnly = true };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
// Only Pump01(10) carries an attribute set that is both historized and alarm-bearing.
|
|
GalaxyObject only = Assert.Single(result.Objects);
|
|
Assert.Equal(10, only.GobjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_TagNameGlob_MatchesAnchoredCaseInsensitive()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
|
|
GalaxyHierarchyQueryResult prefix = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "Pump*" });
|
|
Assert.Equal([10], prefix.Objects.Select(o => o.GobjectId));
|
|
|
|
// Case-insensitive.
|
|
GalaxyHierarchyQueryResult lower = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "pump01" });
|
|
Assert.Equal([10], lower.Objects.Select(o => o.GobjectId));
|
|
|
|
// '?' single-char wildcard: "Pump0?" matches "Pump01".
|
|
GalaxyHierarchyQueryResult single = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "Pump0?" });
|
|
Assert.Equal([10], single.Objects.Select(o => o.GobjectId));
|
|
|
|
// Anchored: a bare substring that is not a prefix matches nothing.
|
|
GalaxyHierarchyQueryResult anchored = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "ump01" });
|
|
Assert.Empty(anchored.Objects);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_CategoryIds_FilterByObjectCategory()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { CategoryIds = { 200, 201 } };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
// category 200 = Pump01(10), category 201 = Valve01(11).
|
|
Assert.Equal([10, 11], result.Objects.Select(o => o.GobjectId).OrderBy(id => id));
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_TemplateChainContains_IsSubstringAndCaseInsensitive()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { TemplateChainContains = { "pump" } };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
GalaxyObject only = Assert.Single(result.Objects);
|
|
Assert.Equal(10, only.GobjectId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_IncludeAttributesDefault_CarriesAttributes()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { TagNameGlob = "Pump*" };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
GalaxyObject pump = Assert.Single(result.Objects);
|
|
Assert.Equal(2, pump.Attributes.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_IncludeAttributesFalse_ReturnsSkeletons()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
DiscoverHierarchyRequest request = new() { TagNameGlob = "Pump*", IncludeAttributes = false };
|
|
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(entry, request);
|
|
|
|
GalaxyObject pump = Assert.Single(result.Objects);
|
|
Assert.Empty(pump.Attributes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_IncludeAttributesFalse_DoesNotMutateTheCachedEntry()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
|
|
// Project with attributes stripped, then again with attributes included.
|
|
GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "Pump*", IncludeAttributes = false });
|
|
GalaxyHierarchyQueryResult included = GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest { TagNameGlob = "Pump*" });
|
|
|
|
// The earlier strip cloned the object — the cached entry still holds the attributes.
|
|
GalaxyObject pump = Assert.Single(included.Objects);
|
|
Assert.Equal(2, pump.Attributes.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void Project_InvalidOffsetOrPageSize_Throws()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest(), null, offset: -1, pageSize: 10));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
GalaxyHierarchyProjector.Project(entry, new DiscoverHierarchyRequest(), null, offset: 0, pageSize: 0));
|
|
}
|
|
|
|
// ---- GalaxyBrowseProjector ----
|
|
|
|
[Fact]
|
|
public void ProjectChildren_OfPlantArea_ReturnsDirectChildrenAreasFirst()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
BrowseChildrenRequest request = new() { ParentGobjectId = 1 };
|
|
|
|
GalaxyBrowseChildrenResult result = GalaxyBrowseProjector.ProjectChildren(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 100);
|
|
|
|
// Direct children of PlantArea(1) are LineA(2, area) and Mixer01(12, non-area);
|
|
// areas sort first.
|
|
Assert.Equal([2, 12], result.Children.Select(c => c.GobjectId));
|
|
Assert.Equal(2, result.TotalChildCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectChildren_ChildHasChildrenFlag_ReflectsPresenceOfChildren()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
BrowseChildrenRequest request = new() { ParentGobjectId = 1 };
|
|
|
|
GalaxyBrowseChildrenResult result = GalaxyBrowseProjector.ProjectChildren(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 100);
|
|
|
|
Dictionary<int, bool> hasChildren = result.Children
|
|
.Select((child, index) => (child.GobjectId, result.ChildHasChildren[index]))
|
|
.ToDictionary(t => t.GobjectId, t => t.Item2);
|
|
|
|
// LineA(2) contains Pump01/Valve01 -> true; Mixer01(12) is a leaf -> false.
|
|
Assert.True(hasChildren[2]);
|
|
Assert.False(hasChildren[12]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectChildren_OfRoot_ReturnsTopLevelObjects()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
// Empty parent oneof => roots (parent id 0).
|
|
BrowseChildrenRequest request = new();
|
|
|
|
GalaxyBrowseChildrenResult result = GalaxyBrowseProjector.ProjectChildren(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 100);
|
|
|
|
// Roots: PlantArea(1, area) and StandaloneTank(20, non-area); areas first.
|
|
Assert.Equal([1, 20], result.Children.Select(c => c.GobjectId));
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectChildren_FilterMatchingDescendant_SurfacesNonMatchingAncestor()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
// Pump01 lives two levels under PlantArea. Browsing PlantArea's children with a
|
|
// Pump glob should still surface LineA (which itself does not match) because it
|
|
// contains a matching descendant.
|
|
BrowseChildrenRequest request = new() { ParentGobjectId = 1, TagNameGlob = "Pump*" };
|
|
|
|
GalaxyBrowseChildrenResult result = GalaxyBrowseProjector.ProjectChildren(entry, request, browseSubtreeGlobs: null, offset: 0, pageSize: 100);
|
|
|
|
GalaxyObject surfaced = Assert.Single(result.Children);
|
|
Assert.Equal(2, surfaced.GobjectId);
|
|
Assert.True(result.ChildHasChildren[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectChildren_UnknownParent_ThrowsNotFound()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
BrowseChildrenRequest request = new() { ParentGobjectId = 99999 };
|
|
|
|
RpcException ex = Assert.Throws<RpcException>(() =>
|
|
GalaxyBrowseProjector.ProjectChildren(entry, request, null, 0, 100));
|
|
Assert.Equal(StatusCode.NotFound, ex.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void ProjectChildren_Paging_SlicesAndPreservesTotal()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
// LineA(2) has two direct children: Pump01, Valve01.
|
|
BrowseChildrenRequest request = new() { ParentGobjectId = 2 };
|
|
|
|
GalaxyBrowseChildrenResult page1 = GalaxyBrowseProjector.ProjectChildren(entry, request, null, offset: 0, pageSize: 1);
|
|
GalaxyBrowseChildrenResult page2 = GalaxyBrowseProjector.ProjectChildren(entry, request, null, offset: 1, pageSize: 1);
|
|
|
|
Assert.Equal(2, page1.TotalChildCount);
|
|
Assert.Single(page1.Children);
|
|
Assert.Single(page2.Children);
|
|
Assert.NotEqual(page1.Children[0].GobjectId, page2.Children[0].GobjectId);
|
|
// Same filter+parent => same signature on both pages.
|
|
Assert.Equal(page1.FilterSignature, page2.FilterSignature);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolveParentId_ByTagName_ResolvesToGobjectId()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = BuildSampleEntry();
|
|
BrowseChildrenRequest request = new() { ParentTagName = "LineA" };
|
|
|
|
int id = GalaxyBrowseProjector.ResolveParentId(entry, request);
|
|
|
|
Assert.Equal(2, id);
|
|
}
|
|
}
|