forked from baron/baron-sso
역할 전환 E2E 및 권한 안내 검증 테스트 추가
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
"lint": "biome check .",
|
"lint": "biome check .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
|
"test:roles": "playwright test tests/devfront-role-switch-report.spec.ts",
|
||||||
"test:ui": "playwright test --ui"
|
"test:ui": "playwright test --ui"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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 { expect, test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
|
type Consent,
|
||||||
installDevApiMock,
|
installDevApiMock,
|
||||||
makeClient,
|
makeClient,
|
||||||
seedAuth,
|
seedAuth,
|
||||||
type Consent,
|
|
||||||
} from "./helpers/devfront-fixtures";
|
} from "./helpers/devfront-fixtures";
|
||||||
|
|
||||||
test.describe("DevFront security and isolation", () => {
|
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("PKCE only app")).toBeVisible();
|
||||||
await expect(page.getByText("Server side App")).not.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);
|
const nowInSeconds = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
await page.addInitScript((issuedAt) => {
|
await page.addInitScript(
|
||||||
const mockOidcUser = {
|
({ issuedAt, injectedRole }) => {
|
||||||
id_token: "playwright-id-token",
|
const mockOidcUser = {
|
||||||
session_state: "playwright-session",
|
id_token: "playwright-id-token",
|
||||||
access_token: "playwright-access-token",
|
session_state: "playwright-session",
|
||||||
refresh_token: "playwright-refresh-token",
|
access_token: "playwright-access-token",
|
||||||
token_type: "Bearer",
|
refresh_token: "playwright-refresh-token",
|
||||||
scope: "openid profile email",
|
token_type: "Bearer",
|
||||||
profile: {
|
scope: "openid profile email",
|
||||||
sub: "playwright-user",
|
profile: {
|
||||||
email: "playwright@example.com",
|
sub: "playwright-user",
|
||||||
name: "Playwright User",
|
email: "playwright@example.com",
|
||||||
},
|
name: "Playwright User",
|
||||||
expires_at: issuedAt + 3600,
|
...(injectedRole ? { role: injectedRole } : {}),
|
||||||
};
|
},
|
||||||
|
expires_at: issuedAt + 3600,
|
||||||
|
};
|
||||||
|
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"oidc.user:http://localhost:5000/oidc:devfront",
|
"oidc.user:http://localhost:5000/oidc:devfront",
|
||||||
JSON.stringify(mockOidcUser),
|
JSON.stringify(mockOidcUser),
|
||||||
);
|
);
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
"oidc.user:http://localhost:5000/oidc/:devfront",
|
"oidc.user:http://localhost:5000/oidc/:devfront",
|
||||||
JSON.stringify(mockOidcUser),
|
JSON.stringify(mockOidcUser),
|
||||||
);
|
);
|
||||||
window.localStorage.setItem("dev_tenant_id", "tenant-a");
|
window.localStorage.setItem("dev_tenant_id", "tenant-a");
|
||||||
}, nowInSeconds);
|
},
|
||||||
|
{ issuedAt: nowInSeconds, injectedRole: role ?? "" },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function json(route: Route, payload: unknown, status = 200) {
|
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