import { expect, test } from "@playwright/test"; const shareToken = "playwright"; function withShareToken(path: string) { return path.includes("?") ? `${path}&token=${shareToken}` : `${path}?token=${shareToken}`; } type TenantFixture = { id: string; type: string; name: string; slug: string; description: string; status: string; parentId?: string; memberCount: number; createdAt: string; updatedAt: string; }; function tenant( id: string, type: string, name: string, slug: string, parentId?: string, ): TenantFixture { return { id, type, name, slug, description: "", status: "active", parentId, memberCount: 1, createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", }; } function user( id: string, name: string, tenantSlug: string, overrides: Record = {}, ) { return { id, email: `${id}@example.com`, name, role: "user", status: "active", tenantSlug, companyCode: tenantSlug, grade: "사원", createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", ...overrides, }; } async function seedOrgfrontAuth(page: Parameters[0]["page"]) { const nowInSeconds = Math.floor(Date.now() / 1000); await page.addInitScript( ({ issuedAt }) => { const mockOidcUser = { id_token: "playwright-id-token", session_state: "playwright-session", access_token: "playwright-access-token", refresh_token: "playwright-refresh-token", token_type: "Bearer", scope: "openid profile email", profile: { sub: "playwright-user", email: "playwright@example.com", name: "Playwright User", role: "tenant_admin", }, expires_at: issuedAt + 3600, }; const storageKeys = [ "user:http://localhost:5000/oidc:orgfront", "user:http://localhost:5000/oidc/:orgfront", "user:http://localhost:5000/oidc:devfront", "user:http://localhost:5000/oidc/:devfront", "user:http://172.16.9.189:5000/oidc:orgfront", "user:http://172.16.9.189:5000/oidc/:orgfront", "oidc.user:http://localhost:5000/oidc:orgfront", "oidc.user:http://localhost:5000/oidc/:orgfront", "oidc.user:http://localhost:5000/oidc:devfront", "oidc.user:http://localhost:5000/oidc/:devfront", "oidc.user:http://172.16.9.189:5000/oidc:orgfront", "oidc.user:http://172.16.9.189:5000/oidc/:orgfront", ]; for (const key of storageKeys) { window.localStorage.setItem(key, JSON.stringify(mockOidcUser)); } window.localStorage.setItem("playwright_auth_bypass", "1"); window.localStorage.setItem("dev_tenant_id", "group-hmac"); }, { issuedAt: nowInSeconds }, ); await page.route("**/oidc/**", async (route) => { const url = route.request().url(); if (url.includes(".well-known/openid-configuration")) { await route.fulfill({ json: { issuer: "http://localhost:5000/oidc", authorization_endpoint: "http://localhost:5000/oidc/auth", token_endpoint: "http://localhost:5000/oidc/token", jwks_uri: "http://localhost:5000/oidc/jwks", userinfo_endpoint: "http://localhost:5000/oidc/userinfo", end_session_endpoint: "http://localhost:5000/oidc/session/end", }, headers: { "Access-Control-Allow-Origin": "*" }, }); return; } if (url.includes("/jwks")) { await route.fulfill({ json: { keys: [] }, headers: { "Access-Control-Allow-Origin": "*" }, }); return; } await route.fulfill({ status: 200, body: "ok", headers: { "Access-Control-Allow-Origin": "*" }, }); }); } async function installOrgPickerApiMock( page: Parameters[0]["page"], ) { const tenants = [ tenant("group-hmac", "COMPANY_GROUP", "HMAC Group", "hmac"), tenant("company-baron", "COMPANY", "Baron", "baron", "group-hmac"), tenant("company-hanmac", "COMPANY", "Hanmac", "hanmac", "group-hmac"), tenant("dept-center", "USER_GROUP", "센터", "center", "company-baron"), tenant( "team-tech-plan", "USER_GROUP", "기술기획", "tech-plan", "dept-center", ), tenant("team-bcmf", "USER_GROUP", "bCMf", "bcmf", "dept-center"), tenant("team-pm", "USER_GROUP", "PM", "pm", "dept-center"), tenant( "dept-eng", "USER_GROUP", "Engineering", "engineering", "company-baron", ), tenant("team-platform", "USER_GROUP", "Platform", "platform", "dept-eng"), tenant("dept-sales", "USER_GROUP", "Sales", "sales", "company-hanmac"), ]; const users = [ user("user-root", "Group User", "hmac"), user("user-baron", "Baron User", "baron"), user("user-eng", "Engineering User", "engineering"), user("user-platform", "Platform User", "platform", { metadata: { employeeNumber: "EMP-9001", skill: "Kubernetes" }, jobTitle: "Platform Engineer", grade: "책임", position: "팀장", }), user("user-sales", "Sales User", "sales"), ]; const orgChartSnapshot = { generatedAt: "2026-06-17T07:10:11Z", tenants, users, }; await page.route("**/api/v1/admin/tenants**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: tenants, total: tenants.length, limit: 10000, offset: 0, }), }); }); await page.route("**/api/v1/admin/users**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: users, total: users.length, limit: 5000, offset: 0, }), }); }); await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify(orgChartSnapshot), }); }); await page.route("**/api/v1/public/orgchart**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ ...orgChartSnapshot, sharedWith: "playwright" }), }); }); } test.beforeEach(async ({ page }) => { await seedOrgfrontAuth(page); await installOrgPickerApiMock(page); }); test("developer navigation exposes chart, picker, and embed preview", async ({ page, }) => { await page.setViewportSize({ width: 1600, height: 900 }); await page.goto(withShareToken("/chart")); await expect(page.getByRole("link", { name: "조직도" })).toBeVisible(); await expect(page.getByRole("link", { name: "조직 선택기" })).toBeVisible(); await expect(page.getByRole("link", { name: "임베딩 검증" })).toBeVisible(); await expect(page.getByTestId("orgchart-render-status-panel")).toHaveText( "2026-06-17 16:10:11 KST", ); let statusPanelBox = await page .getByTestId("orgchart-render-status-panel") .boundingBox(); expect( (page.viewportSize()?.width ?? 1600) - ((statusPanelBox?.x ?? 0) + (statusPanelBox?.width ?? 0)), ).toBeLessThanOrEqual(20); await page.getByRole("link", { name: "조직 선택기" }).click(); await expect(page.getByTestId("orgchart-render-status-panel")).toHaveText( "2026-06-17 16:10:11 KST", ); statusPanelBox = await page .getByTestId("orgchart-render-status-panel") .boundingBox(); expect( (page.viewportSize()?.width ?? 1600) - ((statusPanelBox?.x ?? 0) + (statusPanelBox?.width ?? 0)), ).toBeLessThanOrEqual(20); await page.getByRole("link", { name: "임베딩 검증" }).click(); await expect( page.getByRole("heading", { name: "임베딩 검증" }), ).toBeVisible(); await expect(page.getByTestId("orgchart-render-status-panel")).toHaveText( "2026-06-17 16:10:11 KST", ); statusPanelBox = await page .getByTestId("orgchart-render-status-panel") .boundingBox(); expect( (page.viewportSize()?.width ?? 1600) - ((statusPanelBox?.x ?? 0) + (statusPanelBox?.width ?? 0)), ).toBeLessThanOrEqual(20); await expect( page .frameLocator("iframe") .getByTestId("org-picker-search-section") .getByText("하위 선택"), ).toBeVisible(); }); test("picker menu lets developers switch selection mode and selectable type", async ({ page, }) => { await page.goto(withShareToken("/picker")); await expect(page.getByLabel("선택 모드")).toHaveValue("multiple"); await expect(page.getByLabel("선택 대상")).toHaveValue("both"); await page.getByLabel("선택 모드").selectOption("single"); await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount( 0, ); await page.getByLabel("선택 대상").selectOption("tenant"); const picker = page.frameLocator("iframe"); await expect( picker.getByRole("button", { name: "Engineering User" }), ).toHaveCount(0); await expect( picker.getByRole("button", { name: "Engineering", exact: true }), ).toBeVisible(); }); test("picker defaults to the hanmac-family company-group when no tenant id is supplied", async ({ page, }) => { await page.unroute("**/api/v1/admin/tenants**"); await page.unroute("**/api/v1/admin/users**"); const tenants = [ tenant("wrong-group", "COMPANY_GROUP", "Wrong Group", "wrong-group"), tenant( "wrong-company", "COMPANY", "Wrong Company", "wrong-company", "wrong-group", ), tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"), tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"), ]; await page.route("**/api/v1/admin/tenants**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: tenants, total: tenants.length, limit: 10000, offset: 0, }), }); }); await page.route("**/api/v1/admin/users**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: [], total: 0, limit: 5000, offset: 0, }), }); }); await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ tenants, users: [] }), }); }); await page.route("**/api/v1/public/orgchart**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ tenants, users: [], sharedWith: "playwright" }), }); }); await page.goto(withShareToken("/picker")); const picker = page.frameLocator("iframe"); await expect(picker.getByText("한맥가족", { exact: true })).toBeVisible(); await expect(picker.getByText("삼안", { exact: true })).toBeVisible(); await expect(picker.getByText("Wrong Group", { exact: true })).toHaveCount(0); }); test("embed preview picker orders hanmac-family tenants by the shared policy", async ({ page, }) => { await page.unroute("**/api/v1/admin/tenants**"); await page.unroute("**/api/v1/admin/users**"); const tenants = [ tenant("hanmac-family-id", "COMPANY_GROUP", "한맥가족", "hanmac-family"), tenant( "baron-group-id", "COMPANY_GROUP", "바론그룹", "baron-group", "hanmac-family-id", ), tenant("hanmac-id", "COMPANY", "한맥기술", "hanmac", "hanmac-family-id"), tenant("saman-id", "COMPANY", "삼안", "saman", "hanmac-family-id"), tenant( "gpdtdc-id", "ORGANIZATION", "총괄기획&기술개발센터", "gpdtdc", "hanmac-family-id", ), ]; await page.route("**/api/v1/admin/tenants**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: tenants, total: tenants.length, limit: 10000, offset: 0, }), }); }); await page.route("**/api/v1/admin/users**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: [], total: 0, limit: 5000, offset: 0, }), }); }); await page.route("**/api/v1/admin/orgchart/snapshot**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ tenants, users: [] }), }); }); await page.route("**/api/v1/public/orgchart**", async (route) => { await route.fulfill({ contentType: "application/json", body: JSON.stringify({ tenants, users: [], sharedWith: "playwright" }), }); }); await page.goto(withShareToken("/embed-preview?select=tenant")); await expect( page.frameLocator("iframe").getByTestId("org-picker-node-name-tenant"), ).toHaveText([ "한맥가족", "총괄기획&기술개발센터", "삼안", "한맥기술", "바론그룹", ]); }); test("picker displays user names with grade and optional position", async ({ page, }) => { await page.goto(withShareToken("/embed/picker?mode=single&select=user")); await expect( page.getByRole("button", { name: "Platform User 책임", }), ).toBeVisible(); }); test("embed preview menu updates the iframe picker source", async ({ page, }) => { await page.goto(withShareToken("/embed-preview")); await expect(page.getByLabel("선택 모드")).toHaveValue("multiple"); await expect(page.getByLabel("선택 대상")).toHaveValue("both"); await expect(page.getByTestId("embed-preview-src")).toContainText( "mode=multiple", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "select=both", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "includeDescendants=true", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "showDescendantToggle=true", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "width=400", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "height=600", ); await expect(page.getByTestId("embed-preview-frame-shell")).toHaveCSS( "width", "400px", ); await page.getByLabel("선택 모드").selectOption("single"); await page.getByLabel("선택 대상").selectOption("user"); await expect(page.getByTestId("embed-preview-src")).toContainText( "mode=single", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "select=user", ); await expect(page.getByTestId("embed-preview-src")).not.toContainText( "includeDescendants", ); await expect(page.getByTestId("embed-preview-src")).not.toContainText( "showDescendantToggle", ); await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount( 0, ); await expect( page.frameLocator("iframe").getByRole("button", { name: "Engineering User 사원 user-eng@example.com", }), ).toBeVisible(); }); test("embed preview passes tenant slug and custom dimensions through the picker url", async ({ page, }) => { await page.goto(withShareToken("/embed-preview")); await page.getByLabel("tenant slug").fill("baron"); await page.getByLabel("임베딩 너비").fill("520"); await page.getByLabel("임베딩 높이").fill("480"); await expect(page.getByTestId("embed-preview-src")).toContainText( "tenantSlug=baron", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "width=520", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "height=480", ); await expect(page.getByTestId("embed-preview-frame-shell")).toHaveCSS( "width", "520px", ); const picker = page.frameLocator("iframe"); await expect(picker.getByText("Engineering User")).toBeVisible(); await expect(picker.getByText("Sales User")).toHaveCount(0); }); test("embed picker scopes the tree by tenant slug, hides users for tenant selection, and keeps direct members before child tenants", async ({ page, }) => { await page.goto( withShareToken("/embed-preview?tenantSlug=baron&select=tenant"), ); await expect(page.getByLabel("tenant slug")).toHaveValue("baron"); await expect(page.getByTestId("embed-preview-src")).toContainText( "tenantSlug=baron", ); const picker = page.frameLocator("iframe"); await expect(picker.getByText("Baron", { exact: true })).toBeVisible(); await expect(picker.getByText("Hanmac", { exact: true })).toHaveCount(0); await expect(picker.getByText("Sales User")).toHaveCount(0); await expect(picker.getByText("Baron User")).toHaveCount(0); await page.getByLabel("선택 대상").selectOption("both"); await expect(picker.getByText("Baron User")).toBeVisible(); const memberBox = await picker.getByText("Baron User").boundingBox(); const childTenantBox = await picker .getByText("센터", { exact: true }) .boundingBox(); expect(memberBox).not.toBeNull(); expect(childTenantBox).not.toBeNull(); expect(memberBox?.y ?? 0).toBeLessThan(childTenantBox?.y ?? 0); }); test("embed picker keeps the lightweight search controls inside the picker section at the default embed width", async ({ page, }) => { await page.goto(withShareToken("/embed-preview")); const picker = page.frameLocator("iframe"); const searchSection = picker.getByTestId("org-picker-search-section"); await expect(searchSection).toBeVisible(); await expect(searchSection.getByLabel("company 필터")).toHaveCount(0); await expect(searchSection.getByText("선택 결과")).toHaveCount(0); const searchBox = await searchSection .getByLabel("조직/구성원 검색") .boundingBox(); const descendantToggle = await searchSection .getByTestId("org-picker-descendant-toggle") .boundingBox(); const sectionBox = await searchSection.boundingBox(); expect(searchBox).not.toBeNull(); expect(descendantToggle).not.toBeNull(); expect(sectionBox).not.toBeNull(); expect( Math.abs((searchBox?.y ?? 0) - (descendantToggle?.y ?? 0)), ).toBeLessThanOrEqual(8); expect(sectionBox?.height ?? 0).toBeLessThanOrEqual(72); }); test("embed picker keeps only the lightweight picker surface scrollable", async ({ page, }) => { await page.goto(withShareToken("/embed-preview")); const picker = page.frameLocator("iframe"); await expect( picker.getByRole("heading", { name: "조직 선택기" }), ).toHaveCount(0); await expect(picker.getByTestId("org-picker-search-section")).toBeVisible(); await expect(picker.getByTestId("org-picker-tree-scroll")).toBeVisible(); await expect .poll(async () => picker.locator("body").evaluate((element) => { const style = window.getComputedStyle(element); return `${style.overflowX}/${style.overflowY}`; }), ) .toBe("hidden/hidden"); await expect .poll(async () => picker .getByTestId("org-picker-tree-scroll") .evaluate((element) => window.getComputedStyle(element).overflowY), ) .toBe("auto"); const rootRowHeight = await picker .getByRole("button", { name: "HMAC Group 접기" }) .locator("xpath=..") .evaluate((element) => element.getBoundingClientRect().height); expect(rootRowHeight).toBeLessThanOrEqual(30); }); test("embed preview can hide the descendant selection switch", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=multiple&select=both")); await expect(page.getByLabel("하위 선택 스위치 표시")).toBeChecked(); await page.getByLabel("하위 선택 스위치 표시").uncheck(); await expect(page.getByTestId("embed-preview-src")).toContainText( "includeDescendants=true", ); await expect(page.getByTestId("embed-preview-src")).toContainText( "showDescendantToggle=false", ); await expect(page.frameLocator("iframe").getByText("하위 선택")).toHaveCount( 0, ); }); test("embed picker renders compact tree rows with member emails", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=single&select=user")); const picker = page.frameLocator("iframe"); await expect(picker.getByText("user-eng@example.com")).toBeVisible(); await expect( picker.getByRole("button", { name: "Engineering User 구성원" }), ).toHaveCount(0); await expect( picker.getByRole("button", { name: "Engineering User 사원 user-eng@example.com", }), ).toBeVisible(); }); test("embed picker filters organizations and users by id, name, and metadata", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=multiple&select=both")); const picker = page.frameLocator("iframe"); const search = picker.getByLabel("조직/구성원 검색"); await expect(picker.getByTestId("org-picker-search-section")).toBeVisible(); await expect(search).toBeVisible(); await search.fill("user-platform"); await expect(picker.getByText("Platform User")).toBeVisible(); await expect(picker.getByText("Sales User")).toHaveCount(0); await search.fill("EMP-9001"); await expect(picker.getByText("Platform User")).toBeVisible(); await expect(picker.getByText("Engineering User")).toHaveCount(0); await search.fill("Sales"); await expect(picker.getByText("Sales", { exact: true })).toBeVisible(); await expect(picker.getByText("Sales User")).toBeVisible(); await expect(picker.getByText("Platform User")).toHaveCount(0); }); test("embed picker search does not keep unmatched descendants under a matching organization", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=multiple&select=both")); const picker = page.frameLocator("iframe"); await picker.getByLabel("조직/구성원 검색").fill("센"); await expect(picker.getByText("센터", { exact: true })).toBeVisible(); await expect(picker.getByText("기술기획", { exact: true })).toHaveCount(0); await expect(picker.getByText("bCMf", { exact: true })).toHaveCount(0); await expect(picker.getByText("PM", { exact: true })).toHaveCount(0); }); test("embed picker posts a single user selection with type, id, and name", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=single&select=user")); const picker = page.frameLocator("iframe"); await picker .getByRole("button", { name: "Engineering User 사원 user-eng@example.com" }) .click(); await picker.getByRole("button", { name: "선택 완료" }).click(); const output = page.getByTestId("embed-preview-output"); await expect(output).toContainText('"type": "user"'); await expect(output).toContainText('"id": "user-eng"'); await expect(output).toContainText('"name": "Engineering User 사원"'); await expect(output).not.toContainText("tenantId"); }); test("embed picker lets user-only multi selection check tenants but posts descendant users only", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=multiple&select=user")); const picker = page.frameLocator("iframe"); await picker.getByLabel("Engineering 선택", { exact: true }).check(); await expect( picker.getByLabel("Platform User 책임 선택", { exact: true }), ).toBeChecked(); await picker.getByRole("button", { name: "선택 완료" }).click(); const output = page.getByTestId("embed-preview-output"); await expect(output).toContainText('"id": "user-eng"'); await expect(output).toContainText('"id": "user-platform"'); await expect(output).not.toContainText('"id": "dept-eng"'); await expect(output).not.toContainText('"id": "team-platform"'); }); test("embed picker single selection counts only the selected node without descendants", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=single&select=both")); const picker = page.frameLocator("iframe"); await picker .getByRole("button", { name: "Engineering", exact: true }) .click(); await expect(picker.getByText("1개 항목 선택됨")).toBeVisible(); await expect(picker.getByText("3개 항목 선택됨")).toHaveCount(0); await expect(picker.getByText("4개 항목 선택됨")).toHaveCount(0); await picker.getByRole("button", { name: "선택 완료" }).click(); const output = page.getByTestId("embed-preview-output"); await expect(output).toContainText('"id": "dept-eng"'); await expect(output).not.toContainText('"id": "user-eng"'); await expect(output).not.toContainText('"id": "team-platform"'); await expect(output).not.toContainText('"id": "user-platform"'); }); test("embed picker highlights a single selected item without tree connectors", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=single&select=both")); const picker = page.frameLocator("iframe"); await expect( picker.getByRole("button", { name: "Engineering", exact: true }), ).toBeVisible(); await expect(picker.getByTestId("org-picker-tree-connector")).toHaveCount(0); const selected = picker.getByRole("button", { name: "Engineering", exact: true, }); await selected.click(); await expect(selected).toHaveAttribute("aria-pressed", "true"); await expect(selected).toHaveAttribute("data-selected", "true"); }); test("embed picker renders tenant names with the dedicated tenant text color", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=single&select=both")); const picker = page.frameLocator("iframe"); const tenantName = picker.getByTestId("org-picker-node-name-tenant").first(); await expect(tenantName).toBeVisible(); await expect .poll(() => tenantName.evaluate((element) => window.getComputedStyle(element).color), ) .toBe("rgb(10, 33, 20)"); }); test("embed picker includes descendants by default and can disable descendant inclusion", async ({ page, }) => { await page.goto(withShareToken("/embed-preview?mode=multiple&select=both")); let picker = page.frameLocator("iframe"); await expect( picker.getByTestId("org-picker-search-section").getByText("하위 선택"), ).toBeVisible(); await picker.getByLabel("Engineering 선택").check(); await expect(picker.getByLabel("Platform 선택")).toBeChecked(); await expect(picker.getByLabel("Platform User 책임 선택")).toBeChecked(); await picker.getByRole("button", { name: "선택 완료" }).click(); let output = page.getByTestId("embed-preview-output"); await expect(output).toContainText('"id": "dept-eng"'); await expect(output).toContainText('"id": "team-platform"'); await expect(output).toContainText('"id": "user-platform"'); await page.goto( withShareToken( "/embed-preview?mode=multiple&select=both&includeDescendants=false", ), ); picker = page.frameLocator("iframe"); await picker.getByLabel("Engineering 선택").check(); await expect(picker.getByLabel("Platform 선택")).not.toBeChecked(); await expect(picker.getByLabel("Platform User 책임 선택")).not.toBeChecked(); await picker.getByRole("button", { name: "선택 완료" }).click(); output = page.getByTestId("embed-preview-output"); await expect(output).toContainText('"id": "dept-eng"'); await expect(output).not.toContainText('"id": "team-platform"'); await expect(output).not.toContainText('"id": "user-platform"'); });