using System.Collections.Generic;
using System.Linq;
using GB5Shared.Tree;
using FrameworkDAL.DTO.MenuGroup;
using Xunit;
namespace FrameworkTests;
///
/// Covers the -1-sentinel-to-null-Parent translation MenuGroupBLL.GetMenuGroupTreeForSet
/// applies before calling TreeHelper.ConvertToTree (mirroring MenuBLL's existing MMENU
/// handling), plus the shared/private node-visibility mix and orphaned-parent handling
/// called out in the plan's Phase 4 test list. Pure C# — no DB involved, since
/// TreeHelper itself is DB-agnostic.
///
public class MenuGroupTreeTests
{
private static MenuGroupDTO Node(int id, string code, int parentGroupId, int? menuSetId = null) => new()
{
MenuGroupId = id,
GroupCode = code,
GroupName = code,
ParentGroupId = parentGroupId,
MenuSetId = menuSetId,
};
private static void ApplySentinelTranslation(IEnumerable flat)
{
foreach (var node in flat)
node.Parent = node.ParentGroupId == -1 ? null : new MenuGroupDTO { MenuGroupId = node.ParentGroupId };
}
[Fact]
public void ConvertToTree_TranslatesMinusOneSentinelToRoot()
{
var flat = new List
{
Node(1, "TOP", parentGroupId: -1),
Node(2, "CHILD", parentGroupId: 1),
};
ApplySentinelTranslation(flat);
var forest = flat.ConvertToTree();
var top = Assert.Single(forest);
Assert.Equal(1, top.MenuGroupId);
var child = Assert.Single(top.children);
Assert.Equal(2, child.MenuGroupId);
}
[Fact]
public void ConvertToTree_SharedAndPrivateNodesCoexistInOneForest()
{
// MENUSETID IS NULL = shared bundle node; MENUSETID = 42 = private to that one set.
var flat = new List
{
Node(1, "SHARED_TOP", parentGroupId: -1, menuSetId: null),
Node(2, "SHARED_CHILD", parentGroupId: 1, menuSetId: null),
Node(3, "PRIVATE_CHILD", parentGroupId: 1, menuSetId: 42),
};
ApplySentinelTranslation(flat);
var forest = flat.ConvertToTree();
var top = forest.FindTreeNode(1)!;
Assert.Equal(2, top.children.Count);
Assert.Contains(top.children, c => c.MenuGroupId == 2);
Assert.Contains(top.children, c => c.MenuGroupId == 3 && c.MenuSetId == 42);
}
[Fact]
public void ConvertToTree_OrphanedParent_NodeIsSilentlyDropped()
{
// Node 2's parent (99) never made it into the flat list — e.g. a private group whose
// parent was deleted, or simply not visible to this MenuSetId's filter. TreeHelper's
// documented behavior is to drop the orphan rather than throw.
var flat = new List
{
Node(1, "TOP", parentGroupId: -1),
Node(2, "ORPHAN", parentGroupId: 99),
};
ApplySentinelTranslation(flat);
var forest = flat.ConvertToTree();
Assert.Single(forest);
Assert.Null(forest.FindTreeNode(2));
}
[Fact]
public void FindTreeNode_ReturnsConfiguredRoot_AndDepthFirstTraversalCollectsAllDescendantIds()
{
var flat = new List
{
Node(1, "ROOT", parentGroupId: -1),
Node(2, "CHILD_A", parentGroupId: 1),
Node(3, "CHILD_B", parentGroupId: 1),
Node(4, "GRANDCHILD", parentGroupId: 2),
};
ApplySentinelTranslation(flat);
var forest = flat.ConvertToTree();
var root = forest.FindTreeNode(1);
Assert.NotNull(root);
var ids = root!.DepthFirstTraversal().Select(n => n.MenuGroupId).OrderBy(i => i).ToList();
Assert.Equal(new[] { 1, 2, 3, 4 }, ids);
}
[Fact]
public void FindTreeNode_MisconfiguredRoot_ReturnsNull()
{
// MMENUSET.MenuGroupId pointing at a group id that doesn't exist in the flat list
// (e.g. a private node scoped to a different MenuSetId) must be detectable by the
// BLL as "not found", not silently treated as an empty tree.
var flat = new List { Node(1, "TOP", parentGroupId: -1) };
ApplySentinelTranslation(flat);
var forest = flat.ConvertToTree();
Assert.Null(forest.FindTreeNode(404));
}
}