615b487a77
Adds missing <summary>/<param> XML docs across 99 server, worker, and test files so CommentChecker reports zero issues (TreatWarningsAsErrors needs the analyzer clean). Bundles in WIP dashboard work: NavSection extraction, MainLayout/site.css/js styling alignment, and DashboardOptions/Auth tweaks.
141 lines
5.4 KiB
C#
141 lines
5.4 KiB
C#
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
|
using ZB.MOM.WW.MxGateway.Server.Dashboard;
|
|
using ZB.MOM.WW.MxGateway.Server.Galaxy;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Tests.Galaxy;
|
|
|
|
/// <summary>
|
|
/// Direct coverage for <see cref="GalaxyHierarchyProjector"/> paging.
|
|
/// <para>
|
|
/// Regression guard for finding Server-007: the projector memoizes the filtered,
|
|
/// ordered view list per <c>(cache entry, filter signature)</c> so paging is an
|
|
/// O(pageSize) slice rather than an O(total) re-scan per page. These tests confirm
|
|
/// the memo does not change paging results, does not bleed between distinct filter
|
|
/// signatures, and is scoped to a single cache-entry instance.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class GalaxyHierarchyProjectorTests
|
|
{
|
|
/// <summary>Verifies that paging across a hierarchy returns every object exactly once.</summary>
|
|
[Fact]
|
|
public void Project_PagedAcrossEntireHierarchy_ReturnsEveryObjectExactlyOnce()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(25));
|
|
|
|
List<string> collected = [];
|
|
int totalReported = -1;
|
|
for (int offset = 0; offset < 25; offset += 4)
|
|
{
|
|
GalaxyHierarchyQueryResult result = GalaxyHierarchyProjector.Project(
|
|
entry,
|
|
new DiscoverHierarchyRequest(),
|
|
browseSubtreeGlobs: null,
|
|
offset,
|
|
pageSize: 4);
|
|
|
|
totalReported = result.TotalObjectCount;
|
|
collected.AddRange(result.Objects.Select(obj => obj.TagName));
|
|
}
|
|
|
|
Assert.Equal(25, totalReported);
|
|
Assert.Equal(25, collected.Count);
|
|
Assert.Equal(collected.Count, collected.Distinct(StringComparer.Ordinal).Count());
|
|
Assert.Equal("Object_001", collected[0]);
|
|
Assert.Equal("Object_025", collected[^1]);
|
|
}
|
|
|
|
/// <summary>Verifies that distinct filters on the same entry do not share memoized view list.</summary>
|
|
[Fact]
|
|
public void Project_DistinctFiltersOnSameEntry_DoNotShareMemoizedViewList()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(10));
|
|
|
|
GalaxyHierarchyQueryResult globbed = GalaxyHierarchyProjector.Project(
|
|
entry,
|
|
new DiscoverHierarchyRequest { TagNameGlob = "Object_00?" });
|
|
GalaxyHierarchyQueryResult unfiltered = GalaxyHierarchyProjector.Project(
|
|
entry,
|
|
new DiscoverHierarchyRequest());
|
|
|
|
// Distinct filter signatures must each get their own filtered list.
|
|
Assert.Equal(9, globbed.TotalObjectCount);
|
|
Assert.Equal(10, unfiltered.TotalObjectCount);
|
|
}
|
|
|
|
/// <summary>Verifies that the same filter repeated returns identical totals.</summary>
|
|
[Fact]
|
|
public void Project_SameFilterRepeated_ReturnsIdenticalTotals()
|
|
{
|
|
GalaxyHierarchyCacheEntry entry = CreateEntry(CreateObjects(12));
|
|
|
|
GalaxyHierarchyQueryResult first = GalaxyHierarchyProjector.Project(
|
|
entry,
|
|
new DiscoverHierarchyRequest(),
|
|
browseSubtreeGlobs: null,
|
|
offset: 0,
|
|
pageSize: 5);
|
|
GalaxyHierarchyQueryResult second = GalaxyHierarchyProjector.Project(
|
|
entry,
|
|
new DiscoverHierarchyRequest(),
|
|
browseSubtreeGlobs: null,
|
|
offset: 5,
|
|
pageSize: 5);
|
|
|
|
Assert.Equal(first.TotalObjectCount, second.TotalObjectCount);
|
|
Assert.Equal(first.FilterSignature, second.FilterSignature);
|
|
Assert.Equal(5, first.Objects.Count);
|
|
Assert.Equal(5, second.Objects.Count);
|
|
Assert.NotEqual(first.Objects[0].TagName, second.Objects[0].TagName);
|
|
}
|
|
|
|
/// <summary>Verifies that distinct cache entries project against their own data.</summary>
|
|
[Fact]
|
|
public void Project_DistinctCacheEntries_ProjectAgainstTheirOwnData()
|
|
{
|
|
GalaxyHierarchyCacheEntry small = CreateEntry(CreateObjects(3));
|
|
GalaxyHierarchyCacheEntry large = CreateEntry(CreateObjects(40));
|
|
|
|
GalaxyHierarchyQueryResult smallResult = GalaxyHierarchyProjector.Project(
|
|
small,
|
|
new DiscoverHierarchyRequest());
|
|
GalaxyHierarchyQueryResult largeResult = GalaxyHierarchyProjector.Project(
|
|
large,
|
|
new DiscoverHierarchyRequest());
|
|
|
|
// Each entry instance keys its own memo; the second projection must not reuse the
|
|
// first entry's filtered view list.
|
|
Assert.Equal(3, smallResult.TotalObjectCount);
|
|
Assert.Equal(40, largeResult.TotalObjectCount);
|
|
}
|
|
|
|
private static GalaxyHierarchyCacheEntry CreateEntry(IReadOnlyList<GalaxyObject> objects)
|
|
{
|
|
return GalaxyHierarchyCacheEntry.Empty with
|
|
{
|
|
Status = GalaxyCacheStatus.Healthy,
|
|
Sequence = 1,
|
|
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();
|
|
}
|
|
}
|