1
0
forked from baron/baron-sso

테넌트 목록 조회 cursor기반으로 재구성. 사용자 metadata 미사용 필드 제거

This commit is contained in:
2026-05-13 18:05:51 +09:00
parent a4d707d4d8
commit 5e7b7b878c
85 changed files with 4808 additions and 734 deletions

View File

@@ -105,6 +105,197 @@ test.describe("Tenants Management", () => {
expect(headerWhiteSpace.every((value) => value === "nowrap")).toBe(true);
});
test("should virtualize large tenant lists and load next pages automatically", async ({
page,
}) => {
await page.setViewportSize({ width: 900, height: 700 });
let requestCount = 0;
await page.route("**/api/v1/admin/tenants**", async (route) => {
if (route.request().method() !== "GET") {
return route.continue();
}
const url = new URL(route.request().url());
const cursor = url.searchParams.get("cursor");
requestCount += 1;
if (!cursor) {
return route.fulfill({
json: {
items: Array.from({ length: 500 }, (_, index) => ({
id: `tenant-${String(index + 1).padStart(3, "0")}`,
name: `Tenant ${String(index + 1).padStart(3, "0")}`,
slug: `tenant-${String(index + 1).padStart(3, "0")}`,
status: "active",
type: "COMPANY",
memberCount: 0,
updatedAt: new Date().toISOString(),
})),
total: 501,
limit: 500,
offset: 0,
nextCursor: "next-page",
},
headers: { "Access-Control-Allow-Origin": "*" },
});
}
return route.fulfill({
json: {
items: [
{
id: "tenant-501",
name: "Tenant 501",
slug: "tenant-501",
status: "active",
type: "COMPANY",
memberCount: 0,
updatedAt: new Date().toISOString(),
},
],
total: 501,
limit: 500,
offset: 0,
},
headers: { "Access-Control-Allow-Origin": "*" },
});
});
await page.goto("/tenants");
await expect(page.getByText("총 501개 테넌트")).toBeVisible();
await expect(page.getByRole("button", { name: "더 불러오기" })).toHaveCount(
0,
);
await expect
.poll(async () => page.locator("tbody tr").count())
.toBeLessThan(80);
const tableScroller = page.getByTestId("tenant-table-scroll");
await tableScroller.evaluate((element) => {
element.scrollTop = element.scrollHeight;
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
await expect.poll(() => requestCount).toBe(2);
await tableScroller.evaluate((element) => {
element.scrollTop = element.scrollHeight;
element.dispatchEvent(new Event("scroll", { bubbles: true }));
});
await expect(page.getByText("Tenant 501")).toBeVisible();
expect(requestCount).toBe(2);
});
test("should hide Hanmac family subtree from external tenant admins", async ({
page,
}) => {
await page.route(/.*\/api\/v1\/user\/me$/, async (route) => {
return route.fulfill({
json: {
id: "external-admin",
name: "External Admin",
role: "tenant_admin",
tenantId: "external-tenant-id",
tenantSlug: "external-tenant",
tenant: {
id: "external-tenant-id",
slug: "external-tenant",
name: "External Tenant",
type: "COMPANY",
},
manageableTenants: [
{
id: "external-tenant-id",
slug: "external-tenant",
name: "External Tenant",
type: "COMPANY",
},
{
id: "external-team-id",
slug: "external-team",
name: "External Team",
type: "USER_GROUP",
parentId: "external-tenant-id",
},
],
},
});
});
await page.route("**/api/v1/admin/tenants**", async (route) => {
if (route.request().method() !== "GET") {
await route.continue();
return;
}
await route.fulfill({
json: {
items: [
{
id: "hanmac-family-id",
slug: "hanmac-family",
name: "한맥가족",
status: "active",
type: "COMPANY_GROUP",
memberCount: 0,
},
{
id: "hanmac-company-id",
slug: "hanmac-company",
name: "한맥기술",
status: "active",
type: "COMPANY",
parentId: "hanmac-family-id",
memberCount: 0,
},
{
id: "hanmac-team-id",
slug: "hanmac-team",
name: "한맥팀",
status: "active",
type: "USER_GROUP",
parentId: "hanmac-company-id",
memberCount: 0,
},
{
id: "external-tenant-id",
slug: "external-tenant",
name: "External Tenant",
status: "active",
type: "COMPANY",
memberCount: 0,
},
{
id: "external-team-id",
slug: "external-team",
name: "External Team",
status: "active",
type: "USER_GROUP",
parentId: "external-tenant-id",
memberCount: 0,
},
],
total: 5,
limit: 1000,
offset: 0,
},
headers: { "Access-Control-Allow-Origin": "*" },
});
});
await page.goto("/tenants");
await expect(page.locator("h2").last()).toContainText(
/테넌트 목록|Tenants/i,
{ timeout: 20000 },
);
await expect(page.locator("table")).toContainText("External Tenant");
await expect(page.locator("table")).toContainText("External Team");
await expect(page.locator("table")).not.toContainText("한맥가족");
await expect(page.locator("table")).not.toContainText("한맥기술");
await expect(page.locator("table")).not.toContainText("한맥팀");
});
test("should create a new tenant", async ({ page }) => {
await page.goto("/tenants/new");
await expect(page.locator("h2").last()).toContainText(/추가|Create/i, {