forked from baron/baron-sso
역할 전환 E2E 및 권한 안내 검증 테스트 추가
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
"lint": "biome check .",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"test:roles": "playwright test tests/devfront-role-switch-report.spec.ts",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
141
devfront/tests/devfront-role-switch-report.spec.ts
Normal file
141
devfront/tests/devfront-role-switch-report.spec.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
type AuditLog,
|
||||
type Consent,
|
||||
installDevApiMock,
|
||||
makeClient,
|
||||
seedAuth,
|
||||
} from "./helpers/devfront-fixtures";
|
||||
import { captureEvidence } from "./helpers/evidence";
|
||||
|
||||
test.describe("DevFront role report", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
page.on("dialog", async (dialog) => {
|
||||
await dialog.accept();
|
||||
});
|
||||
});
|
||||
|
||||
test("user(tenant_member) is blocked with 안내 문구", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await seedAuth(page, "user");
|
||||
await installDevApiMock(page, {
|
||||
clients: [],
|
||||
consents: [] as Consent[],
|
||||
auditLogs: [] as AuditLog[],
|
||||
});
|
||||
|
||||
await page.goto("/clients");
|
||||
await expect(
|
||||
page.getByText(/관리자 전용 화면|administrator only/i),
|
||||
).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-user-blocked");
|
||||
});
|
||||
|
||||
test("rp_admin sees only assigned Gitea app and its logs", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await seedAuth(page, "rp_admin");
|
||||
const state = {
|
||||
clients: [makeClient("gitea-client", { name: "Gitea" })],
|
||||
consents: [] as Consent[],
|
||||
auditLogs: [
|
||||
{
|
||||
event_id: "evt-rp-1",
|
||||
timestamp: "2026-03-04T01:00:00.000Z",
|
||||
user_id: "rp-admin-user",
|
||||
event_type: "CLIENT_UPDATE",
|
||||
status: "success" as const,
|
||||
ip_address: "127.0.0.1",
|
||||
user_agent: "playwright",
|
||||
details: JSON.stringify({
|
||||
action: "UPDATE_CLIENT",
|
||||
target_id: "gitea-client",
|
||||
tenant_id: "tenant-a",
|
||||
}),
|
||||
},
|
||||
] as AuditLog[],
|
||||
};
|
||||
await installDevApiMock(page, state);
|
||||
|
||||
await page.goto("/clients");
|
||||
await expect(page.getByRole("link", { name: /Gitea/ })).toBeVisible();
|
||||
await expect(page.getByText("gitea-client")).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-rp-admin-clients");
|
||||
|
||||
await page.goto("/audit-logs");
|
||||
await expect(page.getByText("UPDATE_CLIENT")).toBeVisible();
|
||||
await expect(page.getByText("gitea-client")).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-rp-admin-audit");
|
||||
});
|
||||
|
||||
test("tenant_admin can manage tenant apps and see tenant logs", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await seedAuth(page, "tenant_admin");
|
||||
const state = {
|
||||
clients: [
|
||||
makeClient("tenant-a-app-1", { name: "Tenant A CRM" }),
|
||||
makeClient("tenant-a-app-2", { name: "Tenant A ERP" }),
|
||||
],
|
||||
consents: [] as Consent[],
|
||||
auditLogs: [] as AuditLog[],
|
||||
auditLogsByCursor: undefined,
|
||||
};
|
||||
await installDevApiMock(page, state);
|
||||
|
||||
await page.goto("/clients");
|
||||
await expect(page.getByText("Tenant A CRM")).toBeVisible();
|
||||
await expect(page.getByText("Tenant A ERP")).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-tenant-admin-clients");
|
||||
|
||||
await page.goto("/clients/tenant-a-app-1/settings");
|
||||
await page
|
||||
.getByPlaceholder("My Awesome Application")
|
||||
.fill("Tenant A CRM Updated");
|
||||
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
|
||||
|
||||
await page.goto("/audit-logs");
|
||||
await expect(page.getByText("UPDATE_CLIENT")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await expect(page.getByText("tenant-a-app-1")).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-tenant-admin-audit");
|
||||
});
|
||||
|
||||
test("super_admin sees all and can generate log entries", async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await seedAuth(page, "super_admin");
|
||||
const state = {
|
||||
clients: [
|
||||
makeClient("tenant-a-app", { name: "Tenant A App" }),
|
||||
makeClient("tenant-b-app", { name: "Tenant B App" }),
|
||||
],
|
||||
consents: [] as Consent[],
|
||||
auditLogs: [] as AuditLog[],
|
||||
auditLogsByCursor: undefined,
|
||||
};
|
||||
await installDevApiMock(page, state);
|
||||
|
||||
await page.goto("/clients");
|
||||
await expect(page.getByText("Tenant A App")).toBeVisible();
|
||||
await expect(page.getByText("Tenant B App")).toBeVisible();
|
||||
await captureEvidence(page, testInfo, "role-super-admin-clients");
|
||||
|
||||
await page.goto("/clients/new");
|
||||
await page
|
||||
.getByPlaceholder("My Awesome Application")
|
||||
.fill("Super Admin Created App");
|
||||
await page
|
||||
.getByPlaceholder(/https:\/\/app\.example\.com\/callback/i)
|
||||
.fill("https://super-admin.example.com/callback");
|
||||
await page.getByRole("button", { name: /앱 생성|Create/i }).click();
|
||||
|
||||
await page.goto("/audit-logs");
|
||||
await expect(page.getByText("CREATE_CLIENT")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await captureEvidence(page, testInfo, "role-super-admin-audit");
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
type Consent,
|
||||
installDevApiMock,
|
||||
makeClient,
|
||||
seedAuth,
|
||||
type Consent,
|
||||
} from "./helpers/devfront-fixtures";
|
||||
|
||||
test.describe("DevFront security and isolation", () => {
|
||||
@@ -47,4 +47,14 @@ test.describe("DevFront security and isolation", () => {
|
||||
await expect(page.getByText("PKCE only app")).toBeVisible();
|
||||
await expect(page.getByText("Server side App")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("tenant_member user is blocked at AuthGuard", async ({ page }) => {
|
||||
await seedAuth(page, "tenant_member");
|
||||
|
||||
await page.goto("/clients");
|
||||
await expect(
|
||||
page.getByText(/DevFront는 관리자 전용 화면입니다|administrator access/i),
|
||||
).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/clients$/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,35 +70,39 @@ export function makeClient(
|
||||
};
|
||||
}
|
||||
|
||||
export async function seedAuth(page: Page) {
|
||||
export async function seedAuth(page: Page, role?: string) {
|
||||
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",
|
||||
},
|
||||
expires_at: issuedAt + 3600,
|
||||
};
|
||||
await page.addInitScript(
|
||||
({ issuedAt, injectedRole }) => {
|
||||
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",
|
||||
...(injectedRole ? { role: injectedRole } : {}),
|
||||
},
|
||||
expires_at: issuedAt + 3600,
|
||||
};
|
||||
|
||||
window.localStorage.setItem(
|
||||
"oidc.user:http://localhost:5000/oidc:devfront",
|
||||
JSON.stringify(mockOidcUser),
|
||||
);
|
||||
window.localStorage.setItem(
|
||||
"oidc.user:http://localhost:5000/oidc/:devfront",
|
||||
JSON.stringify(mockOidcUser),
|
||||
);
|
||||
window.localStorage.setItem("dev_tenant_id", "tenant-a");
|
||||
}, nowInSeconds);
|
||||
window.localStorage.setItem(
|
||||
"oidc.user:http://localhost:5000/oidc:devfront",
|
||||
JSON.stringify(mockOidcUser),
|
||||
);
|
||||
window.localStorage.setItem(
|
||||
"oidc.user:http://localhost:5000/oidc/:devfront",
|
||||
JSON.stringify(mockOidcUser),
|
||||
);
|
||||
window.localStorage.setItem("dev_tenant_id", "tenant-a");
|
||||
},
|
||||
{ issuedAt: nowInSeconds, injectedRole: role ?? "" },
|
||||
);
|
||||
}
|
||||
|
||||
function json(route: Route, payload: unknown, status = 200) {
|
||||
|
||||
24
devfront/tests/helpers/evidence.ts
Normal file
24
devfront/tests/helpers/evidence.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { Page, TestInfo } from "@playwright/test";
|
||||
|
||||
function safeName(name: string): string {
|
||||
return name
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-_]+/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
export async function captureEvidence(
|
||||
page: Page,
|
||||
testInfo: TestInfo,
|
||||
name: string,
|
||||
) {
|
||||
const filename = `${safeName(name)}.png`;
|
||||
const fullPath = testInfo.outputPath(filename);
|
||||
await page.screenshot({ path: fullPath, fullPage: true });
|
||||
await testInfo.attach(name, {
|
||||
path: fullPath,
|
||||
contentType: "image/png",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user