forked from baron/baron-sso
chore: consolidate local integration changes
This commit is contained in:
@@ -180,7 +180,7 @@ test.describe("보안 및 접근 제어: 시스템 관리자 vs 일반 사용자
|
||||
await expect(page.locator('a[href="/api-keys"]')).toBeVisible();
|
||||
await expect(page.locator('a[href="/audit-logs"]')).toBeVisible();
|
||||
await expect(
|
||||
page.locator('a[href="/system/projections/users"]'),
|
||||
page.locator('a[href="/system/ory-ssot"]'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.locator('a[href="/system/data-integrity"]'),
|
||||
@@ -209,7 +209,7 @@ test.describe("보안 및 접근 제어: 시스템 관리자 vs 일반 사용자
|
||||
await expect(page.locator('a[href="/tenants"]')).not.toBeVisible();
|
||||
await expect(page.locator('a[href="/api-keys"]')).not.toBeVisible();
|
||||
await expect(
|
||||
page.locator('a[href="/system/projections/users"]'),
|
||||
page.locator('a[href="/system/ory-ssot"]'),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
page.locator('a[href="/system/data-integrity"]'),
|
||||
|
||||
@@ -121,6 +121,105 @@ test.describe("Tenants Management", () => {
|
||||
expect(headerWhiteSpace.every((value) => value === "nowrap")).toBe(true);
|
||||
});
|
||||
|
||||
test("should export currently selected organization users by tenant slug", async ({
|
||||
page,
|
||||
}) => {
|
||||
let exportUrl = "";
|
||||
|
||||
await page.route("**/api/v1/admin/tenants**", async (route) => {
|
||||
if (route.request().method() !== "GET") {
|
||||
return route.continue();
|
||||
}
|
||||
const url = new URL(route.request().url());
|
||||
if (url.pathname.endsWith("/admin/tenants/tenant-company")) {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
id: "tenant-company",
|
||||
name: "GPDTDC",
|
||||
slug: "gpdtdc",
|
||||
type: "COMPANY",
|
||||
status: "active",
|
||||
},
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
}
|
||||
return route.fulfill({
|
||||
json: {
|
||||
items: [
|
||||
{
|
||||
id: "tenant-company",
|
||||
name: "GPDTDC",
|
||||
slug: "gpdtdc",
|
||||
type: "COMPANY",
|
||||
status: "active",
|
||||
memberCount: 1,
|
||||
recursiveMemberCount: 1,
|
||||
},
|
||||
{
|
||||
id: "tenant-team",
|
||||
parentId: "tenant-company",
|
||||
name: "기술연구팀",
|
||||
slug: "gpdtdc-rnd",
|
||||
type: "ORGANIZATION",
|
||||
status: "active",
|
||||
memberCount: 1,
|
||||
recursiveMemberCount: 1,
|
||||
},
|
||||
],
|
||||
total: 2,
|
||||
limit: 1000,
|
||||
offset: 0,
|
||||
},
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
});
|
||||
|
||||
await page.route(/\/admin\/users(\?.*)?$/, async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
expect(url.searchParams.get("tenantSlug")).toBe("gpdtdc");
|
||||
return route.fulfill({
|
||||
json: {
|
||||
items: [
|
||||
{
|
||||
id: "user-1",
|
||||
name: "Member User",
|
||||
email: "member@example.com",
|
||||
role: "user",
|
||||
status: "active",
|
||||
tenantSlug: "gpdtdc",
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
},
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
});
|
||||
});
|
||||
|
||||
await page.route(/\/admin\/users\/export(\?.*)?$/, async (route) => {
|
||||
exportUrl = route.request().url();
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "text/csv; charset=utf-8",
|
||||
"content-disposition": 'attachment; filename="tenant-users.csv"',
|
||||
},
|
||||
body: "email,name\nmember@example.com,Member User\n",
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/tenants/tenant-company/organization");
|
||||
await expect(page.getByText("Member User")).toBeVisible();
|
||||
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download"),
|
||||
page.getByTestId("tenant-current-users-export-btn").click(),
|
||||
]);
|
||||
|
||||
expect(download.suggestedFilename()).toBe("tenant-users.csv");
|
||||
expect(exportUrl).toContain("tenantSlug=gpdtdc");
|
||||
expect(exportUrl).toContain("includeIds=false");
|
||||
});
|
||||
|
||||
test("searches tenant ids in the tree view and selects descendants", async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -141,7 +240,8 @@ test.describe("Tenants Management", () => {
|
||||
slug: "acme",
|
||||
status: "active",
|
||||
type: "COMPANY",
|
||||
memberCount: 0,
|
||||
memberCount: 3,
|
||||
totalMemberCount: 9,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
@@ -151,7 +251,8 @@ test.describe("Tenants Management", () => {
|
||||
status: "active",
|
||||
type: "ORGANIZATION",
|
||||
parentId: "company-1",
|
||||
memberCount: 0,
|
||||
memberCount: 4,
|
||||
totalMemberCount: 6,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
@@ -161,19 +262,31 @@ test.describe("Tenants Management", () => {
|
||||
status: "active",
|
||||
type: "USER_GROUP",
|
||||
parentId: "dept-1",
|
||||
memberCount: 0,
|
||||
memberCount: 2,
|
||||
totalMemberCount: 2,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
let filtered = items;
|
||||
if (search) {
|
||||
filtered = items.filter(
|
||||
const directMatches = items.filter(
|
||||
(i) =>
|
||||
i.name.toLowerCase().includes(search) ||
|
||||
i.slug.toLowerCase().includes(search) ||
|
||||
i.id.toLowerCase().includes(search),
|
||||
);
|
||||
const ids = new Set(directMatches.map((item) => item.id));
|
||||
for (const match of directMatches) {
|
||||
let parentId = match.parentId;
|
||||
while (parentId) {
|
||||
const parent = items.find((item) => item.id === parentId);
|
||||
if (!parent) break;
|
||||
ids.add(parent.id);
|
||||
parentId = parent.parentId;
|
||||
}
|
||||
}
|
||||
filtered = items.filter((item) => ids.has(item.id));
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
@@ -192,7 +305,14 @@ test.describe("Tenants Management", () => {
|
||||
await page
|
||||
.getByPlaceholder(/이름 또는 슬러그, ID 검색|search/i)
|
||||
.fill("team-1");
|
||||
await expect(page.locator("table")).toContainText("Acme");
|
||||
await expect(page.locator("table")).toContainText("Planning");
|
||||
await expect(page.locator("table")).toContainText("Platform");
|
||||
await expect(page.getByTestId("tenant-search-match-team-1")).toBeVisible();
|
||||
await expect(page.getByTestId("tenant-search-match-company-1")).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(page.getByTestId("tenant-search-match-dept-1")).toHaveCount(0);
|
||||
|
||||
await page.getByPlaceholder(/이름 또는 슬러그, ID 검색|search/i).fill("");
|
||||
await page
|
||||
@@ -226,7 +346,8 @@ test.describe("Tenants Management", () => {
|
||||
slug: "acme",
|
||||
status: "active",
|
||||
type: "COMPANY",
|
||||
memberCount: 0,
|
||||
memberCount: 3,
|
||||
totalMemberCount: 9,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
@@ -236,7 +357,8 @@ test.describe("Tenants Management", () => {
|
||||
status: "active",
|
||||
type: "ORGANIZATION",
|
||||
parentId: "company-1",
|
||||
memberCount: 0,
|
||||
memberCount: 4,
|
||||
totalMemberCount: 6,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
@@ -246,7 +368,8 @@ test.describe("Tenants Management", () => {
|
||||
status: "active",
|
||||
type: "USER_GROUP",
|
||||
parentId: "dept-1",
|
||||
memberCount: 0,
|
||||
memberCount: 2,
|
||||
totalMemberCount: 2,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
@@ -280,6 +403,11 @@ test.describe("Tenants Management", () => {
|
||||
"aria-pressed",
|
||||
"true",
|
||||
);
|
||||
await expect(
|
||||
page
|
||||
.getByTestId("tenant-internal-id-company-1")
|
||||
.locator("xpath=ancestor::tr"),
|
||||
).toContainText("9명");
|
||||
|
||||
await page.getByPlaceholder(/UUID|슬러그|slug/i).fill("team-1");
|
||||
await page.keyboard.press("Enter");
|
||||
@@ -743,16 +871,18 @@ test.describe("Tenants Management", () => {
|
||||
let exportUrl = "";
|
||||
let importRequested = false;
|
||||
let importBody = "";
|
||||
const openDataManagementMenu = async () => {
|
||||
const openDataManagementMenu = async (
|
||||
expectedTestId = "tenant-export-menu-item",
|
||||
) => {
|
||||
const btn = page.getByTestId("tenant-data-mgmt-btn");
|
||||
const exportMenuItem = page.getByTestId("tenant-export-menu-item");
|
||||
const expectedMenuItem = page.getByTestId(expectedTestId);
|
||||
|
||||
// Attempt to open the menu with a retry loop using toPass
|
||||
await expect(async () => {
|
||||
if (!(await exportMenuItem.isVisible())) {
|
||||
if (!(await expectedMenuItem.isVisible())) {
|
||||
await btn.click({ force: true });
|
||||
}
|
||||
await expect(exportMenuItem).toBeVisible({ timeout: 2000 });
|
||||
await expect(expectedMenuItem).toBeVisible({ timeout: 2000 });
|
||||
}).toPass({
|
||||
intervals: [1000, 2000],
|
||||
timeout: 10000,
|
||||
@@ -847,7 +977,7 @@ test.describe("Tenants Management", () => {
|
||||
|
||||
await expect(page.getByText(/조직\/사용자 통합/)).toHaveCount(0);
|
||||
|
||||
await openDataManagementMenu();
|
||||
await openDataManagementMenu("tenant-export-menu-item");
|
||||
await expect(page.getByTestId("tenant-template-menu-item")).toBeVisible();
|
||||
await expect(page.getByTestId("tenant-import-menu-item")).toBeVisible();
|
||||
|
||||
@@ -872,14 +1002,14 @@ test.describe("Tenants Management", () => {
|
||||
expect(exportDownload.suggestedFilename()).toBe("tenants.csv");
|
||||
expect(exportUrl).toContain("includeIds=false");
|
||||
|
||||
await openDataManagementMenu();
|
||||
await openDataManagementMenu("tenant-export-with-ids-menu-item");
|
||||
await expect(
|
||||
page.getByTestId("tenant-export-with-ids-menu-item"),
|
||||
).toBeVisible();
|
||||
await safeDownload("tenant-export-with-ids-menu-item");
|
||||
expect(exportUrl).toContain("includeIds=true");
|
||||
|
||||
await openDataManagementMenu();
|
||||
await openDataManagementMenu("tenant-template-menu-item");
|
||||
const template = await safeDownload("tenant-template-menu-item");
|
||||
expect(template.suggestedFilename()).toBe("tenant-import-template.csv");
|
||||
|
||||
|
||||
@@ -315,6 +315,185 @@ test.describe("User Management", () => {
|
||||
await expect(page.getByText(/저장/i).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("should manage global custom claim permissions in user detail", async ({
|
||||
page,
|
||||
}) => {
|
||||
let updatePayload: Record<string, unknown> | undefined;
|
||||
|
||||
await page.route(/\/admin\/users\/u-1$/, async (route) => {
|
||||
const method = route.request().method();
|
||||
|
||||
if (method === "GET") {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
id: "u-1",
|
||||
name: "John Doe",
|
||||
email: "john@test.com",
|
||||
loginId: "johndoe",
|
||||
tenantSlug: "test-tenant",
|
||||
tenant: { id: "t-1", name: "Test Tenant", slug: "test-tenant" },
|
||||
role: "user",
|
||||
status: "active",
|
||||
metadata: {
|
||||
"t-1": { loginId: "johndoe" },
|
||||
global_custom_claims: {
|
||||
contract_date: "2026-06-09",
|
||||
},
|
||||
global_custom_claim_types: {
|
||||
contract_date: "date",
|
||||
},
|
||||
global_custom_claim_permissions: {
|
||||
contract_date: {
|
||||
readPermission: "user_and_admin",
|
||||
writePermission: "admin_only",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (method === "PUT") {
|
||||
updatePayload = route.request().postDataJSON();
|
||||
return route.fulfill({
|
||||
json: {
|
||||
id: "u-1",
|
||||
name: "John Doe",
|
||||
email: "john@test.com",
|
||||
loginId: "johndoe",
|
||||
status: "active",
|
||||
metadata: updatePayload?.metadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return route.fallback();
|
||||
});
|
||||
|
||||
await page.goto("/users/u-1");
|
||||
await page
|
||||
.getByRole("tab", { name: /전역 Custom Claims|Custom Claims/i })
|
||||
.click();
|
||||
|
||||
await expect(
|
||||
page.getByTestId("global-custom-claim-key-contract_date"),
|
||||
).toHaveValue("contract_date");
|
||||
await expect(
|
||||
page.getByTestId("global-custom-claim-read-permission-contract_date"),
|
||||
).toHaveValue("user_and_admin");
|
||||
await expect(
|
||||
page.getByTestId("global-custom-claim-write-permission-contract_date"),
|
||||
).toHaveValue("admin_only");
|
||||
|
||||
await page
|
||||
.getByTestId("global-custom-claim-write-permission-contract_date")
|
||||
.selectOption("user_and_admin");
|
||||
|
||||
await page.screenshot({
|
||||
path: "test-results/adminfront-global-custom-claim-permissions.png",
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: /전역 Claim 저장|Save Global Claim/i })
|
||||
.click();
|
||||
|
||||
await expect
|
||||
.poll(() => updatePayload)
|
||||
.toMatchObject({
|
||||
metadata: {
|
||||
global_custom_claims: {
|
||||
contract_date: "2026-06-09",
|
||||
},
|
||||
global_custom_claim_types: {
|
||||
contract_date: "date",
|
||||
},
|
||||
global_custom_claim_permissions: {
|
||||
contract_date: {
|
||||
readPermission: "user_and_admin",
|
||||
writePermission: "user_and_admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("should configure global custom claim definitions", async ({ page }) => {
|
||||
let updatePayload:
|
||||
| {
|
||||
items?: Array<Record<string, unknown>>;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
await page.route(/\/admin\/global-custom-claims$/, async (route) => {
|
||||
const method = route.request().method();
|
||||
|
||||
if (method === "GET") {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
items: [
|
||||
{
|
||||
key: "contract_date",
|
||||
label: "Contract date",
|
||||
valueType: "date",
|
||||
readPermission: "user_and_admin",
|
||||
writePermission: "admin_only",
|
||||
description: "전체 RP에 공통 제공되는 계약일",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (method === "PUT") {
|
||||
updatePayload = route.request().postDataJSON();
|
||||
return route.fulfill({ json: updatePayload });
|
||||
}
|
||||
|
||||
return route.fallback();
|
||||
});
|
||||
|
||||
await page.goto("/users/custom-claims");
|
||||
|
||||
await expect(page.getByText("전역 Claim 설정")).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("global-claim-definition-key-contract_date"),
|
||||
).toHaveValue("contract_date");
|
||||
await expect(
|
||||
page.getByTestId("global-claim-definition-read-permission-contract_date"),
|
||||
).toHaveValue("user_and_admin");
|
||||
await expect(
|
||||
page.getByTestId(
|
||||
"global-claim-definition-write-permission-contract_date",
|
||||
),
|
||||
).toHaveValue("admin_only");
|
||||
|
||||
await page
|
||||
.getByTestId("global-claim-definition-write-permission-contract_date")
|
||||
.selectOption("user_and_admin");
|
||||
|
||||
await page.screenshot({
|
||||
path: "test-results/adminfront-global-custom-claim-definition-settings.png",
|
||||
fullPage: true,
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
|
||||
|
||||
await expect
|
||||
.poll(() => updatePayload)
|
||||
.toMatchObject({
|
||||
items: [
|
||||
{
|
||||
key: "contract_date",
|
||||
label: "Contract date",
|
||||
valueType: "date",
|
||||
readPermission: "user_and_admin",
|
||||
writePermission: "user_and_admin",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("should show conflict error when updating to an existing Login ID", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("Worksmobile tenant management", () => {
|
||||
@@ -32,7 +31,10 @@ test.describe("Worksmobile tenant management", () => {
|
||||
page,
|
||||
}) => {
|
||||
const comparisonRequests: boolean[] = [];
|
||||
const syncRequests: string[] = [];
|
||||
const syncRequests: Array<{
|
||||
userId: string;
|
||||
body: Record<string, unknown>;
|
||||
}> = [];
|
||||
|
||||
await page.route("**/api/v1/**", async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
@@ -218,7 +220,13 @@ test.describe("Worksmobile tenant management", () => {
|
||||
isWorksmobileTenantPath("/worksmobile/users/user-missing/sync") &&
|
||||
method === "POST"
|
||||
) {
|
||||
syncRequests.push("user-missing");
|
||||
syncRequests.push({
|
||||
userId: "user-missing",
|
||||
body: JSON.parse(route.request().postData() ?? "{}") as Record<
|
||||
string,
|
||||
unknown
|
||||
>,
|
||||
});
|
||||
return route.fulfill({
|
||||
json: { id: "job-user-missing", resourceId: "user-missing" },
|
||||
headers,
|
||||
@@ -235,7 +243,8 @@ test.describe("Worksmobile tenant management", () => {
|
||||
await expect(page.getByRole("tab", { name: "조직" })).toBeVisible();
|
||||
|
||||
await page.getByRole("tab", { name: "이력" }).click();
|
||||
await expect(page.getByText("비밀번호 파일 히스토리")).toBeVisible();
|
||||
await expect(page.getByText("비밀번호 파일 히스토리")).not.toBeVisible();
|
||||
await expect(page.getByText("최근 작업")).toBeVisible();
|
||||
await expect(page.getByText("domainMappings")).not.toBeVisible();
|
||||
await expect(page.getByText("SCIM token")).not.toBeVisible();
|
||||
await page.getByRole("tab", { name: "사용자" }).click();
|
||||
@@ -246,6 +255,9 @@ test.describe("Worksmobile tenant management", () => {
|
||||
"worksmobile-구성원-virtual-body",
|
||||
);
|
||||
await expect(userComparisonTable).toBeVisible();
|
||||
await expect(page.getByTestId("worksmobile-구성원-row-count")).toHaveText(
|
||||
"표시 2 / 전체 5",
|
||||
);
|
||||
await expect(userSyncCard).toBeVisible();
|
||||
expect(
|
||||
await page.evaluate(() => {
|
||||
@@ -347,7 +359,17 @@ test.describe("Worksmobile tenant management", () => {
|
||||
await page
|
||||
.getByRole("button", { name: "선택 구성원 WORKS에 생성" })
|
||||
.click();
|
||||
await expect.poll(() => syncRequests).toEqual(["user-missing"]);
|
||||
await expect(page.getByText("WORKS 초기 비밀번호")).toBeVisible();
|
||||
await page.getByLabel("초기 비밀번호").fill("InitPass123!");
|
||||
await page.getByRole("button", { name: "생성 작업 등록" }).click();
|
||||
await expect
|
||||
.poll(() => syncRequests)
|
||||
.toEqual([
|
||||
{
|
||||
userId: "user-missing",
|
||||
body: expect.objectContaining({ initialPassword: "InitPass123!" }),
|
||||
},
|
||||
]);
|
||||
|
||||
await page.getByRole("tab", { name: "조직" }).click();
|
||||
await expect(page.getByText("조직 단건 동기화")).toBeVisible();
|
||||
@@ -357,6 +379,9 @@ test.describe("Worksmobile tenant management", () => {
|
||||
"worksmobile-조직/그룹-virtual-body",
|
||||
);
|
||||
await expect(groupComparisonTable).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("worksmobile-조직/그룹-row-count"),
|
||||
).toHaveText("표시 2 / 전체 2");
|
||||
await expect(groupSyncCard).toBeVisible();
|
||||
expect(
|
||||
await page.evaluate(() => {
|
||||
@@ -381,6 +406,228 @@ test.describe("Worksmobile tenant management", () => {
|
||||
await expect(page.getByText("works-parent-tech")).toBeVisible();
|
||||
});
|
||||
|
||||
test("separates selected user create and update actions", async ({
|
||||
page,
|
||||
}) => {
|
||||
const syncRequests: Array<{
|
||||
userId: string;
|
||||
body: Record<string, unknown>;
|
||||
}> = [];
|
||||
|
||||
await page.route("**/api/v1/**", async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
const method = route.request().method();
|
||||
const headers = { "Access-Control-Allow-Origin": "*" };
|
||||
const isWorksmobileTenantPath = (suffix: string) =>
|
||||
url.pathname.endsWith(`/admin/tenants/hanmac-family-id${suffix}`) ||
|
||||
url.pathname.endsWith(
|
||||
`/admin/tenants/038326b6-954a-48a7-a85f-efd83f62b82a${suffix}`,
|
||||
);
|
||||
|
||||
if (url.pathname.endsWith("/user/me")) {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
id: "admin-user",
|
||||
name: "Admin",
|
||||
role: "super_admin",
|
||||
manageableTenants: [
|
||||
{
|
||||
id: "038326b6-954a-48a7-a85f-efd83f62b82a",
|
||||
name: "한맥 가족",
|
||||
slug: "hanmac-family",
|
||||
type: "COMPANY_GROUP",
|
||||
},
|
||||
],
|
||||
},
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
url.pathname.endsWith("/admin/tenants/hanmac-family-id") &&
|
||||
method === "GET"
|
||||
) {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
id: "hanmac-family-id",
|
||||
name: "한맥 가족",
|
||||
slug: "hanmac-family",
|
||||
type: "COMPANY_GROUP",
|
||||
status: "active",
|
||||
parentId: null,
|
||||
},
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (isWorksmobileTenantPath("/worksmobile") && method === "GET") {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
tenant: {
|
||||
id: "hanmac-family-id",
|
||||
name: "한맥 가족",
|
||||
slug: "hanmac-family",
|
||||
type: "COMPANY_GROUP",
|
||||
status: "active",
|
||||
memberCount: 0,
|
||||
createdAt: "2026-05-04T00:00:00Z",
|
||||
updatedAt: "2026-05-04T00:00:00Z",
|
||||
},
|
||||
config: {
|
||||
enabled: true,
|
||||
tokenConfigured: true,
|
||||
},
|
||||
recentJobs: [],
|
||||
},
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isWorksmobileTenantPath("/worksmobile/credential-batches") &&
|
||||
method === "GET"
|
||||
) {
|
||||
return route.fulfill({ json: [], headers });
|
||||
}
|
||||
|
||||
if (
|
||||
isWorksmobileTenantPath("/worksmobile/comparison") &&
|
||||
method === "GET"
|
||||
) {
|
||||
return route.fulfill({
|
||||
json: {
|
||||
users: [
|
||||
{
|
||||
resourceType: "USER",
|
||||
baronId: "user-missing",
|
||||
baronName: "김생성",
|
||||
status: "missing_in_worksmobile",
|
||||
},
|
||||
{
|
||||
resourceType: "USER",
|
||||
baronId: "user-update",
|
||||
baronName: "이업데이트",
|
||||
baronEmail: "domain@typo.example.com",
|
||||
worksmobileId: "works-user-update",
|
||||
externalKey: "user-update",
|
||||
worksmobileName: "이업데이트",
|
||||
worksmobileEmail: "domain@example.com",
|
||||
status: "needs_update",
|
||||
},
|
||||
],
|
||||
groups: [],
|
||||
},
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isWorksmobileTenantPath("/worksmobile/users/user-missing/sync") &&
|
||||
method === "POST"
|
||||
) {
|
||||
syncRequests.push({
|
||||
userId: "user-missing",
|
||||
body: JSON.parse(route.request().postData() ?? "{}") as Record<
|
||||
string,
|
||||
unknown
|
||||
>,
|
||||
});
|
||||
return route.fulfill({
|
||||
json: { id: "job-user-missing", resourceId: "user-missing" },
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isWorksmobileTenantPath("/worksmobile/users/user-update/sync") &&
|
||||
method === "POST"
|
||||
) {
|
||||
syncRequests.push({
|
||||
userId: "user-update",
|
||||
body: JSON.parse(route.request().postData() ?? "{}") as Record<
|
||||
string,
|
||||
unknown
|
||||
>,
|
||||
});
|
||||
return route.fulfill({
|
||||
json: { id: "job-user-update", resourceId: "user-update" },
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
return route.fulfill({ json: { items: [], total: 0 }, headers });
|
||||
});
|
||||
|
||||
await page.goto("/worksmobile");
|
||||
await page.getByRole("tab", { name: "사용자" }).click();
|
||||
|
||||
const userComparisonSection = page
|
||||
.getByRole("heading", { name: "구성원" })
|
||||
.locator("xpath=ancestor::div[contains(@class, 'space-y-2')][1]");
|
||||
await expect(userComparisonSection.getByText("김생성")).toBeVisible();
|
||||
await expect(userComparisonSection.getByText("이업데이트")).toHaveCount(2);
|
||||
|
||||
const statusHeader = userComparisonSection
|
||||
.locator("thead th")
|
||||
.filter({ hasText: "상태" })
|
||||
.locator("div")
|
||||
.first();
|
||||
await expect
|
||||
.poll(() =>
|
||||
statusHeader.evaluate((element) => {
|
||||
const style = window.getComputedStyle(element);
|
||||
return { alignItems: style.alignItems, display: style.display };
|
||||
}),
|
||||
)
|
||||
.toEqual({ alignItems: "center", display: "flex" });
|
||||
|
||||
await page
|
||||
.getByRole("row", { name: /김생성/ })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
await page
|
||||
.getByRole("row", { name: /이업데이트/ })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
|
||||
await userComparisonSection
|
||||
.getByRole("button", { name: "선택 구성원 WORKS에 생성" })
|
||||
.click();
|
||||
await expect(page.getByText("WORKS 초기 비밀번호")).toBeVisible();
|
||||
await page.getByLabel("초기 비밀번호").fill("InitPass123!");
|
||||
await page.getByRole("button", { name: "생성 작업 등록" }).click();
|
||||
await expect
|
||||
.poll(() => syncRequests)
|
||||
.toEqual([
|
||||
{
|
||||
userId: "user-missing",
|
||||
body: expect.objectContaining({ initialPassword: "InitPass123!" }),
|
||||
},
|
||||
]);
|
||||
|
||||
await page
|
||||
.getByRole("row", { name: /이업데이트/ })
|
||||
.getByRole("checkbox")
|
||||
.check();
|
||||
await userComparisonSection
|
||||
.getByRole("button", { name: "선택 구성원 업데이트 적용" })
|
||||
.click();
|
||||
await expect
|
||||
.poll(() => syncRequests)
|
||||
.toEqual([
|
||||
{
|
||||
userId: "user-missing",
|
||||
body: expect.objectContaining({ initialPassword: "InitPass123!" }),
|
||||
},
|
||||
{
|
||||
userId: "user-update",
|
||||
body: expect.not.objectContaining({
|
||||
initialPassword: expect.anything(),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("shows a toast when selected WORKS creation fails", async ({ page }) => {
|
||||
await page.route("**/api/v1/**", async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
@@ -651,9 +898,7 @@ test.describe("Worksmobile tenant management", () => {
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test("downloads initial password CSV and enqueues WORKS admin jobs", async ({
|
||||
page,
|
||||
}) => {
|
||||
test("shows WORKS job history and enqueues admin jobs", async ({ page }) => {
|
||||
const requests: string[] = [];
|
||||
const headers = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
@@ -790,24 +1035,6 @@ test.describe("Worksmobile tenant management", () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
url.pathname.endsWith(
|
||||
"/admin/tenants/038326b6-954a-48a7-a85f-efd83f62b82a/worksmobile/initial-passwords.csv",
|
||||
) &&
|
||||
method === "GET"
|
||||
) {
|
||||
requests.push("download-passwords");
|
||||
return route.fulfill({
|
||||
body: "email,password\nuser@example.com,Secret123!\n",
|
||||
contentType: "text/csv",
|
||||
headers: {
|
||||
...headers,
|
||||
"Content-Disposition":
|
||||
'attachment; filename="worksmobile-passwords.csv"',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
url.pathname.endsWith(
|
||||
"/admin/tenants/038326b6-954a-48a7-a85f-efd83f62b82a/worksmobile/backfill/dry-run",
|
||||
@@ -864,18 +1091,10 @@ test.describe("Worksmobile tenant management", () => {
|
||||
await page.goto("/worksmobile");
|
||||
await expect(page.getByText("Worksmobile 연동")).toBeVisible();
|
||||
await page.getByRole("tab", { name: "이력" }).click();
|
||||
|
||||
const download = page.waitForEvent("download");
|
||||
await page
|
||||
.getByRole("button", { name: "batch-1 비밀번호 CSV 다운로드" })
|
||||
.click();
|
||||
const passwordCsv = await download;
|
||||
expect(passwordCsv.suggestedFilename()).toBe("worksmobile-passwords.csv");
|
||||
const passwordCsvPath = await passwordCsv.path();
|
||||
expect(passwordCsvPath).toBeTruthy();
|
||||
expect(await readFile(passwordCsvPath ?? "", "utf8")).toContain(
|
||||
"user@example.com,Secret123!",
|
||||
);
|
||||
await expect(page.getByText("비밀번호 파일 히스토리")).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: /비밀번호 CSV 다운로드/ }),
|
||||
).toHaveCount(0);
|
||||
|
||||
await page.getByRole("button", { name: "Backfill Dry-run" }).click();
|
||||
await expect.poll(() => requests).toContain("dry-run");
|
||||
@@ -915,6 +1134,5 @@ test.describe("Worksmobile tenant management", () => {
|
||||
page.once("dialog", (dialog) => dialog.accept());
|
||||
await page.getByRole("button", { name: /대기중 payload 삭제/ }).click();
|
||||
await expect.poll(() => requests).toContain("delete-pending");
|
||||
expect(requests).toContain("download-passwords");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user