1
0
forked from baron/baron-sso

계층 표시 테스트 코드 추가

This commit is contained in:
2026-02-27 10:52:11 +09:00
parent ca45a14bae
commit f02ba3cbbd
6 changed files with 271 additions and 58 deletions

View File

@@ -65,11 +65,7 @@ import {
updateUser,
} from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
type TenantNode = TenantSummary & {
children: TenantNode[];
recursiveMemberCount: number;
};
import { type TenantNode, buildTenantFullTree } from "../../../lib/tenantTree";
const getTenantIcon = (type?: string) => {
switch (type?.toUpperCase()) {
@@ -779,58 +775,10 @@ function TenantUserGroupsTab() {
const allTenants = data?.items ?? [];
const { currentBase, subTree } = useMemo(() => {
if (allTenants.length === 0) return { currentBase: null, subTree: [] };
const tenantMap = new Map<string, TenantNode>();
for (const t of allTenants) {
tenantMap.set(t.id, {
...t,
children: [],
recursiveMemberCount: t.memberCount || 0,
});
}
// Build initial children relations
for (const t of allTenants) {
if (t.parentId) {
const parent = tenantMap.get(t.parentId);
const child = tenantMap.get(t.id);
if (parent && child) {
parent.children.push(child);
}
}
}
// Function to calculate recursive counts
const calculateRecursive = (node: TenantNode): number => {
let total = node.memberCount || 0;
for (const child of node.children) {
total += calculateRecursive(child);
}
node.recursiveMemberCount = total;
return total;
};
// Calculate for all root nodes (those without parent or top-level in current context)
for (const node of tenantMap.values()) {
// We only strictly need to calculate from the top-most nodes to cover everything
if (!node.parentId) {
calculateRecursive(node);
}
}
// Re-calculate specifically for our current tenant to be sure if it wasn't a global root
const base = tenantMap.get(tenantId);
if (base) {
calculateRecursive(base);
}
return {
currentBase: base || null,
subTree: base ? base.children : [],
};
}, [allTenants, tenantId]);
const { currentBase, subTree } = useMemo(
() => buildTenantFullTree(allTenants, tenantId),
[allTenants, tenantId],
);
const handleAdd = (id: string) =>
updateParentMutation.mutate({ id, parentId: tenantId });

View File

@@ -0,0 +1,93 @@
import { describe, expect, it } from "vitest";
import type { TenantSummary } from "./adminApi";
import { buildTenantFullTree } from "./tenantTree";
describe("tenantTree utility", () => {
const mockTenants: TenantSummary[] = [
{
id: "root-1",
name: "Root",
slug: "root",
type: "COMPANY",
memberCount: 10,
parentId: undefined,
description: "",
status: "active",
createdAt: "",
updatedAt: "",
},
{
id: "child-1",
name: "Child 1",
slug: "child-1",
type: "USER_GROUP",
memberCount: 5,
parentId: "root-1",
description: "",
status: "active",
createdAt: "",
updatedAt: "",
},
{
id: "grandchild-1",
name: "Grandchild 1",
slug: "grandchild-1",
type: "USER_GROUP",
memberCount: 2,
parentId: "child-1",
description: "",
status: "active",
createdAt: "",
updatedAt: "",
},
];
it("calculates recursive member counts correctly", () => {
const { currentBase } = buildTenantFullTree(mockTenants, "root-1");
expect(currentBase).not.toBeNull();
if (currentBase) {
// Direct: 10, Child: 5, Grandchild: 2 -> Total: 17
expect(currentBase.recursiveMemberCount).toBe(17);
expect(currentBase.children).toHaveLength(1);
const child = currentBase.children[0];
// Direct: 5, Grandchild: 2 -> Total: 7
expect(child.recursiveMemberCount).toBe(7);
expect(child.children).toHaveLength(1);
const grandchild = child.children[0];
// Direct: 2 -> Total: 2
expect(grandchild.recursiveMemberCount).toBe(2);
expect(grandchild.children).toHaveLength(0);
}
});
it("returns null currentBase if rootId is not found", () => {
const { currentBase } = buildTenantFullTree(mockTenants, "non-existent");
expect(currentBase).toBeNull();
});
it("builds correct structure with multiple roots", () => {
const multiRootTenants: TenantSummary[] = [
...mockTenants,
{
id: "root-2",
name: "Root 2",
slug: "root-2",
type: "COMPANY",
memberCount: 3,
parentId: undefined,
description: "",
status: "active",
createdAt: "",
updatedAt: "",
},
];
const { subTree } = buildTenantFullTree(multiRootTenants);
expect(subTree).toHaveLength(2);
expect(subTree.map(n => n.id)).toContain("root-1");
expect(subTree.map(n => n.id)).toContain("root-2");
});
});

View File

@@ -0,0 +1,69 @@
import type { TenantSummary } from "./adminApi";
export type TenantNode = TenantSummary & {
children: TenantNode[];
recursiveMemberCount: number;
};
/**
* Builds a hierarchical tree from a flat list of tenants and calculates
* direct and recursive member counts for each node.
*/
export function buildTenantFullTree(
allTenants: TenantSummary[],
rootId?: string,
): { currentBase: TenantNode | null; subTree: TenantNode[] } {
if (allTenants.length === 0) return { currentBase: null, subTree: [] };
const tenantMap = new Map<string, TenantNode>();
for (const t of allTenants) {
tenantMap.set(t.id, {
...t,
children: [],
recursiveMemberCount: t.memberCount || 0,
});
}
// Build initial children relations
for (const t of allTenants) {
if (t.parentId) {
const parent = tenantMap.get(t.parentId);
const child = tenantMap.get(t.id);
if (parent && child) {
parent.children.push(child);
}
}
}
// Function to calculate recursive counts
const calculateRecursive = (node: TenantNode): number => {
let total = node.memberCount || 0;
for (const child of node.children) {
total += calculateRecursive(child);
}
node.recursiveMemberCount = total;
return total;
};
// Calculate for all top-level nodes (those without parent)
for (const node of tenantMap.values()) {
if (!node.parentId) {
calculateRecursive(node);
}
}
// If a specific rootId is provided, find and return its subtree
if (rootId) {
const base = tenantMap.get(rootId);
if (base) {
// Re-calculate specifically for our current tenant to be sure if it wasn't a global root
calculateRecursive(base);
return { currentBase: base, subTree: base.children };
}
return { currentBase: null, subTree: [] };
}
// If no rootId, return all top-level roots as subTree
const roots = Array.from(tenantMap.values()).filter((n) => !n.parentId);
return { currentBase: null, subTree: roots };
}