1
0
forked from baron/baron-sso

chore: consolidate local integration changes

This commit is contained in:
2026-06-09 21:03:05 +09:00
parent aa2848c3b6
commit 1341f07ef9
158 changed files with 10995 additions and 1490 deletions

View File

@@ -0,0 +1,124 @@
import { mkdir } from "node:fs/promises";
import path from "node:path";
import { expect, type Page, type TestInfo, test } from "@playwright/test";
import {
type Consent,
installDevApiMock,
makeClient,
seedAuth,
} from "./helpers/devfront-fixtures";
import { captureEvidence } from "./helpers/evidence";
const existingTenantId = "11111111-1111-4111-8111-111111111111";
const addedTenantId = "22222222-2222-4222-8222-222222222222";
async function captureTenantAccessEvidence(
page: Page,
testInfo: TestInfo,
name: string,
) {
await captureEvidence(page, testInfo, name);
const evidenceDir = path.join(process.cwd(), "e2e-evidence");
await mkdir(evidenceDir, { recursive: true });
await page.screenshot({
path: path.join(evidenceDir, `${name}.png`),
fullPage: true,
});
}
test.describe("DevFront client tenant access settings", () => {
test.beforeEach(async ({ page }) => {
page.on("dialog", async (dialog) => {
await dialog.accept();
});
await seedAuth(page);
});
test("adds and removes allowed tenants with UUID copy evidence", async ({
context,
page,
}, testInfo) => {
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
const state = {
clients: [
makeClient("client-tenant-access", {
name: "Tenant Access App",
scopes: ["openid", "profile", "email"],
metadata: {
tenant_access_restricted: true,
allowed_tenants: [existingTenantId],
},
}),
],
consents: [] as Consent[],
auditLogsByCursor: undefined,
tenants: [
{
id: existingTenantId,
name: "Alpha Tenant",
slug: "alpha",
description: "Existing allowed tenant",
type: "organization",
},
{
id: addedTenantId,
name: "Beta Tenant",
slug: "beta",
description: "Tenant added during E2E",
type: "organization",
},
],
};
await installDevApiMock(page, state);
await page.goto("/clients/client-tenant-access/settings");
await expect(
page.getByRole("heading", { name: /테넌트 접근 제한|Tenant access/i }),
).toBeVisible();
await expect(
page.getByTestId(`allowed-tenant-${existingTenantId}`),
).toContainText(existingTenantId);
await page.getByTestId(`allowed-tenant-copy-${existingTenantId}`).click();
await expect
.poll(() => page.evaluate(() => navigator.clipboard.readText()))
.toBe(existingTenantId);
await page
.getByPlaceholder(/테넌트 이름 또는 슬러그로 검색|tenant name or slug/i)
.fill("beta");
await page.getByRole("button", { name: /Beta Tenant/i }).click();
await expect(
page.getByTestId(`allowed-tenant-${addedTenantId}`),
).toContainText(addedTenantId);
await captureTenantAccessEvidence(
page,
testInfo,
"tenant-access-allowed-tenant-added",
);
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
await expect
.poll(() => state.clients[0]?.metadata?.allowed_tenants)
.toEqual([existingTenantId, addedTenantId]);
await page.getByTestId(`allowed-tenant-remove-${addedTenantId}`).click();
await expect(
page.getByTestId(`allowed-tenant-${addedTenantId}`),
).toHaveCount(0);
await expect(
page.getByTestId(`allowed-tenant-${existingTenantId}`),
).toContainText(existingTenantId);
await captureTenantAccessEvidence(
page,
testInfo,
"tenant-access-allowed-tenant-deleted",
);
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
await expect
.poll(() => state.clients[0]?.metadata?.allowed_tenants)
.toEqual([existingTenantId]);
});
});

View File

@@ -144,29 +144,31 @@ test.describe("DevFront clients lifecycle", () => {
await page.goto("/clients/client-claims/settings");
await page.getByRole("button", { name: /Claim 추가|Add Claim/i }).click();
await page.getByPlaceholder(/e\.g\. locale|예: locale/i).fill("locale");
await expect(page.getByText("rp_claims").first()).toBeVisible();
await expect(
page.getByLabel(/Claim namespace|Claim 네임스페이스/i),
).toHaveCount(0);
await page
.getByLabel(/Claim namespace|Claim 네임스페이스/i)
.first()
.selectOption("top_level");
.getByPlaceholder(/e\.g\. locale|예: locale/i)
.fill("contract_date");
await page
.getByLabel(/Claim value type|Claim 값 타입/i)
.first()
.selectOption("text");
.selectOption("date");
await page
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
.first()
.fill("ko-KR");
.fill("2026-06-09");
await page
.getByLabel(/읽기 권한|Read permission/i)
.first()
.selectOption("user_and_admin");
await page.getByRole("button", { name: /Claim 추가|Add Claim/i }).click();
await page
.getByPlaceholder(/e\.g\. locale|예: locale/i)
.nth(1)
.fill("tier");
await page
.getByLabel(/Claim namespace|Claim 네임스페이스/i)
.nth(1)
.selectOption("rp_claims");
await page
.getByLabel(/Claim value type|Claim 값 타입/i)
.nth(1)
@@ -210,7 +212,7 @@ test.describe("DevFront clients lifecycle", () => {
| undefined
)?.[0]?.namespace,
)
.toBe("top_level");
.toBe("rp_claims");
await expect
.poll(
() =>
@@ -225,7 +227,50 @@ test.describe("DevFront clients lifecycle", () => {
| undefined
)?.[0]?.key,
)
.toBe("locale");
.toBe("contract_date");
await expect
.poll(
() =>
(
state.clients[0]?.metadata?.id_token_claims as
| Array<{
namespace?: string;
key?: string;
value?: string;
valueType?: string;
readPermission?: string;
writePermission?: string;
}>
| undefined
)?.[0]?.valueType,
)
.toBe("date");
await expect
.poll(
() =>
(
state.clients[0]?.metadata?.id_token_claims as
| Array<{
readPermission?: string;
writePermission?: string;
}>
| undefined
)?.[0]?.readPermission,
)
.toBe("user_and_admin");
await expect
.poll(
() =>
(
state.clients[0]?.metadata?.id_token_claims as
| Array<{
readPermission?: string;
writePermission?: string;
}>
| undefined
)?.[0]?.writePermission,
)
.toBe("admin_only");
await expect
.poll(
() =>
@@ -263,7 +308,7 @@ test.describe("DevFront clients lifecycle", () => {
).toHaveCount(2);
await expect(
page.getByPlaceholder(/e\.g\. locale|예: locale/i).first(),
).toHaveValue("locale");
).toHaveValue("contract_date");
await expect(
page.getByPlaceholder(/e\.g\. locale|예: locale/i).nth(1),
).toHaveValue("tier");
@@ -274,7 +319,7 @@ test.describe("DevFront clients lifecycle", () => {
page
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
.first(),
).toHaveValue("ko-KR");
).toHaveValue("2026-06-09");
await expect(
page
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)

View File

@@ -23,7 +23,27 @@ test.describe("DevFront consents", () => {
test("consent list and revoke flow", async ({ page }) => {
const state = {
clients: [makeClient("client-consent", { name: "Consent app" })],
clients: [
makeClient("client-consent", {
name: "Consent app",
metadata: {
id_token_claims: [
{
namespace: "rp_claims",
key: "contract_date",
valueType: "date",
value: "2026-06-09",
},
{
namespace: "rp_claims",
key: "approved_at",
valueType: "datetime",
value: "2026-06-09T09:30",
},
],
},
}),
],
consents: [
{
subject: "user-1",
@@ -36,6 +56,13 @@ test.describe("DevFront consents", () => {
status: "active",
tenantId: "tenant-a",
tenantName: "Tenant A",
rpMetadata: {
approvalLevel: "A",
approvalLevel_permissions: {
readPermission: "admin_only",
writePermission: "admin_only",
},
},
},
] as Consent[],
auditLogsByCursor: undefined,
@@ -45,6 +72,36 @@ test.describe("DevFront consents", () => {
await page.goto("/clients/client-consent/consents");
await expect(page.getByText("Alice")).toBeVisible();
await expect(page.getByText("Tenant A")).toBeVisible();
await expect(page.getByText(/approvalLevel:\s*A/)).toBeVisible();
await page.getByRole("button", { name: /Claims|Claim/i }).click();
await expect(page.getByText("RP Custom Claims")).toBeVisible();
await expect(page.getByText("contract_date")).toBeVisible();
await expect(page.getByText("approved_at")).toBeVisible();
await expect(page.locator('input[type="date"]')).toHaveValue("2026-06-09");
await page.locator('input[type="date"]').fill("2026-06-10");
await page.locator('input[type="datetime-local"]').fill("2026-06-09T10:30");
await page
.getByLabel(/쓰기 권한|Write permission/i)
.first()
.selectOption("user_and_admin");
await page.getByRole("button", { name: /Claim 저장|Save Claim/i }).click();
await expect
.poll(() => state.consents[0]?.rpMetadata?.contract_date)
.toBe("2026-06-10");
await expect
.poll(() => state.consents[0]?.rpMetadata?.approved_at)
.toBe("2026-06-09T10:30");
await expect
.poll(
() =>
(
state.consents[0]?.rpMetadata?.contract_date_permissions as
| Record<string, unknown>
| undefined
)?.writePermission,
)
.toBe("user_and_admin");
await page.getByRole("button", { name: /권한 철회|철회|Revoke/i }).click();
await expect(page.getByText(/Revoked|철회/i).first()).toBeVisible();

View File

@@ -51,6 +51,7 @@ export type Consent = {
status: "active" | "revoked";
tenantId: string;
tenantName: string;
rpMetadata?: Record<string, unknown>;
};
export type DeveloperRequestStatus = "pending" | "approved" | "rejected";
@@ -89,6 +90,14 @@ export type DevAssignableUser = {
loginId?: string;
};
export type DevTenantSummary = {
id: string;
name: string;
slug: string;
description?: string;
type?: string;
};
export type AuditLog = {
event_id: string;
timestamp: string;
@@ -106,6 +115,7 @@ export type DevApiMockState = {
developerRequests?: DeveloperRequest[];
relations?: Record<string, ClientRelation[]>;
users?: DevAssignableUser[];
tenants?: DevTenantSummary[];
auditLogsByCursor?: Record<
string,
{ items: AuditLog[]; next_cursor?: string }
@@ -397,9 +407,12 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
}
if (pathname === "/api/v1/dev/my-tenants" && method === "GET") {
return json(route, [
{ id: "tenant-a", name: "Tenant A", slug: "tenant-a" },
]);
return json(
route,
state.tenants ?? [
{ id: "tenant-a", name: "Tenant A", slug: "tenant-a" },
],
);
}
if (pathname === "/api/v1/dev/stats" && method === "GET") {
@@ -602,6 +615,50 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
});
}
if (
pathname.startsWith("/api/v1/dev/clients/") &&
pathname.includes("/users/") &&
pathname.endsWith("/metadata") &&
method === "GET"
) {
const parts = pathname.split("/").filter(Boolean);
const clientId = parts[4] ?? "";
const userId = parts[6] ?? "";
const target = state.consents.find(
(row) => row.clientId === clientId && row.subject === userId,
);
return json(route, {
clientId,
userId,
metadata: target?.rpMetadata ?? {},
});
}
if (
pathname.startsWith("/api/v1/dev/clients/") &&
pathname.includes("/users/") &&
pathname.endsWith("/metadata") &&
method === "PUT"
) {
const parts = pathname.split("/").filter(Boolean);
const clientId = parts[4] ?? "";
const userId = parts[6] ?? "";
const payload = (request.postDataJSON() as {
metadata?: Record<string, unknown>;
}) || { metadata: {} };
const target = state.consents.find(
(row) => row.clientId === clientId && row.subject === userId,
);
if (target) {
target.rpMetadata = payload.metadata ?? {};
}
return json(route, {
clientId,
userId,
metadata: payload.metadata ?? {},
});
}
if (pathname.startsWith("/api/v1/dev/clients/") && method === "PUT") {
const clientId = parseClientId(pathname);
const payload = (request.postDataJSON() as {