using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
using ZB.MOM.WW.MxGateway.Server.Galaxy;
namespace ZB.MOM.WW.MxGateway.Tests.Galaxy;
///
/// Coverage for — the parent→children
/// index used by the lazy browse projector (Task 3). Verifies root grouping, nested
/// parent→child linkage, corrupt self-parented row handling, and the areas-first
/// ordering rule shared with DashboardBrowseTreeBuilder.
///
public sealed class GalaxyHierarchyIndexTests
{
/// Verifies roots (ParentGobjectId == 0) bucket under sentinel key 0.
[Fact]
public void ChildrenByParent_RootsUnderSentinelZero()
{
GalaxyObject root1 = new() { GobjectId = 1, ParentGobjectId = 0, ContainedName = "r1" };
GalaxyObject root2 = new() { GobjectId = 2, ParentGobjectId = 0, ContainedName = "r2" };
GalaxyObject root3 = new() { GobjectId = 3, ParentGobjectId = 0, ContainedName = "r3" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root1, root2, root3]);
Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList? roots));
Assert.NotNull(roots);
Assert.Equal(3, roots!.Count);
Assert.Contains(roots, view => view.Object.GobjectId == 1);
Assert.Contains(roots, view => view.Object.GobjectId == 2);
Assert.Contains(roots, view => view.Object.GobjectId == 3);
}
/// Verifies a nested A→B→C chain links each parent to its single child bucket.
[Fact]
public void ChildrenByParent_NestedHierarchy_LinksParentToChildren()
{
GalaxyObject areaA = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "A" };
GalaxyObject objB = new() { GobjectId = 2, ParentGobjectId = 1, ContainedName = "B" };
GalaxyObject objC = new() { GobjectId = 3, ParentGobjectId = 2, ContainedName = "C" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([areaA, objB, objC]);
Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList? underRoot));
Assert.NotNull(underRoot);
Assert.Single(underRoot!);
Assert.Equal(1, underRoot![0].Object.GobjectId);
Assert.True(index.ChildrenByParent.TryGetValue(1, out IReadOnlyList? underA));
Assert.NotNull(underA);
Assert.Single(underA!);
Assert.Equal(2, underA![0].Object.GobjectId);
Assert.True(index.ChildrenByParent.TryGetValue(2, out IReadOnlyList? underB));
Assert.NotNull(underB);
Assert.Single(underB!);
Assert.Equal(3, underB![0].Object.GobjectId);
}
/// Verifies a self-parented (corrupt) row appears under root, not under itself.
[Fact]
public void ChildrenByParent_SelfParentedObject_AppearsAsRoot()
{
GalaxyObject selfParented = new() { GobjectId = 5, ParentGobjectId = 5, ContainedName = "loop" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([selfParented]);
Assert.True(index.ChildrenByParent.TryGetValue(0, out IReadOnlyList? roots));
Assert.NotNull(roots);
Assert.Single(roots!);
Assert.Equal(5, roots![0].Object.GobjectId);
// The self-parented row must not appear as its own child — bucket either absent or empty.
if (index.ChildrenByParent.TryGetValue(5, out IReadOnlyList? underSelf))
{
Assert.Empty(underSelf!);
}
}
/// Verifies is OrdinalIgnoreCase and supports O(1) lookups.
[Fact]
public void ObjectViewsByTagName_IsCaseInsensitive_AndLookupsAreO1()
{
GalaxyObject root = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Plant", BrowseName = "Plant", TagName = "Plant" };
GalaxyObject mixer = new() { GobjectId = 2, ParentGobjectId = 1, ContainedName = "Mixer_001", BrowseName = "Mixer_001", TagName = "Plant.Mixer_001" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root, mixer]);
Assert.True(index.ObjectViewsByTagName.TryGetValue("Plant.Mixer_001", out GalaxyObjectView? exact));
Assert.NotNull(exact);
Assert.Equal(2, exact!.Object.GobjectId);
// Case-insensitive lookup must hit the same entry.
Assert.True(index.ObjectViewsByTagName.TryGetValue("plant.mixer_001", out GalaxyObjectView? lower));
Assert.NotNull(lower);
Assert.Same(exact, lower);
Assert.False(index.ObjectViewsByTagName.ContainsKey("Plant.Missing"));
}
/// Verifies is OrdinalIgnoreCase.
[Fact]
public void ObjectViewsByContainedPath_IsCaseInsensitive()
{
GalaxyObject root = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Plant", BrowseName = "Plant", TagName = "Plant" };
GalaxyObject lineA = new() { GobjectId = 2, ParentGobjectId = 1, IsArea = true, ContainedName = "Line_A", BrowseName = "Line_A", TagName = "Plant.Line_A" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([root, lineA]);
Assert.True(index.ObjectViewsByContainedPath.TryGetValue("Plant/Line_A", out GalaxyObjectView? exact));
Assert.NotNull(exact);
Assert.Equal(2, exact!.Object.GobjectId);
Assert.True(index.ObjectViewsByContainedPath.TryGetValue("plant/line_a", out GalaxyObjectView? lower));
Assert.NotNull(lower);
Assert.Same(exact, lower);
Assert.False(index.ObjectViewsByContainedPath.ContainsKey("Plant/Missing"));
}
/// Verifies children sort areas-first, then by display name (case-insensitive).
[Fact]
public void ChildrenByParent_SortsAreasFirstThenByDisplayName()
{
GalaxyObject parent = new() { GobjectId = 1, ParentGobjectId = 0, IsArea = true, ContainedName = "Root" };
GalaxyObject zebraObj = new() { GobjectId = 10, ParentGobjectId = 1, IsArea = false, ContainedName = "zebra" };
GalaxyObject alphaArea = new() { GobjectId = 11, ParentGobjectId = 1, IsArea = true, ContainedName = "alpha" };
GalaxyObject betaArea = new() { GobjectId = 12, ParentGobjectId = 1, IsArea = true, ContainedName = "beta" };
GalaxyHierarchyIndex index = GalaxyHierarchyIndex.Build([parent, zebraObj, alphaArea, betaArea]);
Assert.True(index.ChildrenByParent.TryGetValue(1, out IReadOnlyList? children));
Assert.NotNull(children);
Assert.Equal(3, children!.Count);
Assert.Equal(11, children[0].Object.GobjectId);
Assert.Equal(12, children[1].Object.GobjectId);
Assert.Equal(10, children[2].Object.GobjectId);
}
}