forked from baron/baron-sso
개발자 권한 신청 및 관리 기능 E2E 테스트 추가
This commit is contained in:
144
devfront/tests/devfront-developer-request.spec.ts
Normal file
144
devfront/tests/devfront-developer-request.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import {
|
||||||
|
type DeveloperRequest,
|
||||||
|
installDevApiMock,
|
||||||
|
seedAuth,
|
||||||
|
} from "./helpers/devfront-fixtures";
|
||||||
|
import { captureEvidence } from "./helpers/evidence";
|
||||||
|
|
||||||
|
test.describe("DevFront developer request and management", () => {
|
||||||
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
|
if (testInfo.status === "passed") {
|
||||||
|
await captureEvidence(page, testInfo, testInfo.title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
page.on("dialog", async (dialog) => {
|
||||||
|
await dialog.accept();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("user can request developer access when no RP exists", async ({ page }) => {
|
||||||
|
const state = {
|
||||||
|
clients: [],
|
||||||
|
consents: [],
|
||||||
|
developerRequests: [],
|
||||||
|
};
|
||||||
|
await seedAuth(page, "user");
|
||||||
|
await installDevApiMock(page, state);
|
||||||
|
|
||||||
|
await page.goto("/clients");
|
||||||
|
|
||||||
|
// Click Request Button
|
||||||
|
const requestBtn = page.getByRole('button', { name: /개발자 등록 신청/ });
|
||||||
|
await requestBtn.waitFor({ state: 'visible' });
|
||||||
|
await requestBtn.click();
|
||||||
|
|
||||||
|
// Fill Form (using direct selectors for reliability)
|
||||||
|
await page.locator("#org").fill("QA Team");
|
||||||
|
await page.locator("#reason").fill("Need to test OIDC integration");
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
await page.getByRole('button', { name: /신청하기|Submit/ }).click();
|
||||||
|
|
||||||
|
// Verify Status - Look for "Pending" or "대기" anywhere
|
||||||
|
await expect(page.locator("body")).toContainText(/대기|Pending/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("super admin can approve, reject and cancel developer requests", async ({ page }) => {
|
||||||
|
const request: DeveloperRequest = {
|
||||||
|
id: "req-admin-test",
|
||||||
|
userId: "user-1",
|
||||||
|
userName: "Requester User",
|
||||||
|
name: "Requester User",
|
||||||
|
userEmail: "user1@example.com",
|
||||||
|
organization: "Dev Team",
|
||||||
|
reason: "API Test",
|
||||||
|
status: "pending",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
clients: [],
|
||||||
|
consents: [],
|
||||||
|
developerRequests: [request],
|
||||||
|
};
|
||||||
|
|
||||||
|
await seedAuth(page, "super_admin");
|
||||||
|
await installDevApiMock(page, state);
|
||||||
|
|
||||||
|
await page.goto("/developer-requests");
|
||||||
|
|
||||||
|
// Wait for data to load
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await expect(page.locator("table")).toContainText("Requester User", { timeout: 10000 });
|
||||||
|
|
||||||
|
// Approve
|
||||||
|
const approveBtn = page.getByRole('button', { name: '승인' }).first();
|
||||||
|
await approveBtn.click();
|
||||||
|
await expect(page.locator("table")).toContainText(/승인됨|Approved/);
|
||||||
|
|
||||||
|
// Cancel approval (Requires notes)
|
||||||
|
await page.locator("input.h-8").first().fill("Cancellation reason");
|
||||||
|
await page.getByRole('button', { name: '승인 취소' }).click();
|
||||||
|
await expect(page.locator("table")).toContainText(/대기|Pending/);
|
||||||
|
|
||||||
|
// Reject (Requires notes)
|
||||||
|
await page.locator("input.h-8").first().fill("Rejection reason");
|
||||||
|
await page.getByRole('button', { name: '반려' }).click();
|
||||||
|
await expect(page.locator("table")).toContainText(/반려됨|Rejected/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("approved user can see 'Add App' guidance and create RP", async ({ page }) => {
|
||||||
|
const request: DeveloperRequest = {
|
||||||
|
id: "req-approved",
|
||||||
|
userId: "playwright-user",
|
||||||
|
userName: "Playwright User",
|
||||||
|
name: "Playwright User",
|
||||||
|
userEmail: "playwright@example.com",
|
||||||
|
organization: "QA",
|
||||||
|
reason: "Test",
|
||||||
|
status: "approved",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
approvedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
clients: [],
|
||||||
|
consents: [],
|
||||||
|
developerRequests: [request],
|
||||||
|
};
|
||||||
|
|
||||||
|
await seedAuth(page, "rp_admin");
|
||||||
|
await installDevApiMock(page, state);
|
||||||
|
|
||||||
|
await page.goto("/clients");
|
||||||
|
|
||||||
|
// Click Add App
|
||||||
|
const createBtn = page.getByRole('button', { name: /연동 앱 추가/ }).first();
|
||||||
|
await createBtn.click();
|
||||||
|
|
||||||
|
// Fill Form (Must fill all mandatory fields to enable Submit)
|
||||||
|
await expect(page).toHaveURL(/\/clients\/new$/);
|
||||||
|
|
||||||
|
const nameInput = page.locator("input[placeholder*='Awesome']");
|
||||||
|
await nameInput.fill("E2E Test RP");
|
||||||
|
await nameInput.press('Tab');
|
||||||
|
|
||||||
|
const uriInput = page.locator("textarea.font-mono");
|
||||||
|
await uriInput.fill("https://example.com/callback");
|
||||||
|
await uriInput.press('Tab');
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
const submitBtn = page.getByRole('button', { name: /생성/ }).filter({ hasNotText: '취소' });
|
||||||
|
await expect(submitBtn).toBeEnabled({ timeout: 10000 });
|
||||||
|
await submitBtn.click();
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
await expect(page).toHaveURL(/\/clients\/client-\d+\/settings$/);
|
||||||
|
await expect(page.locator("h1")).toContainText(/설정|Settings/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -53,6 +53,25 @@ export type Consent = {
|
|||||||
tenantName: string;
|
tenantName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DeveloperRequestStatus = "pending" | "approved" | "rejected";
|
||||||
|
|
||||||
|
export type DeveloperRequest = {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
name?: string; // 추가
|
||||||
|
userEmail: string;
|
||||||
|
organization: string;
|
||||||
|
reason: string;
|
||||||
|
status: DeveloperRequestStatus;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
approvedAt?: string;
|
||||||
|
rejectedAt?: string;
|
||||||
|
comment?: string;
|
||||||
|
adminNotes?: string; // 추가
|
||||||
|
};
|
||||||
|
|
||||||
export type ClientRelation = {
|
export type ClientRelation = {
|
||||||
relation: string;
|
relation: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
@@ -84,6 +103,7 @@ export type AuditLog = {
|
|||||||
export type DevApiMockState = {
|
export type DevApiMockState = {
|
||||||
clients: Client[];
|
clients: Client[];
|
||||||
consents: Consent[];
|
consents: Consent[];
|
||||||
|
developerRequests?: DeveloperRequest[];
|
||||||
relations?: Record<string, ClientRelation[]>;
|
relations?: Record<string, ClientRelation[]>;
|
||||||
users?: DevAssignableUser[];
|
users?: DevAssignableUser[];
|
||||||
auditLogsByCursor?: Record<
|
auditLogsByCursor?: Record<
|
||||||
@@ -241,6 +261,79 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
|||||||
const { pathname, searchParams } = url;
|
const { pathname, searchParams } = url;
|
||||||
const method = request.method();
|
const method = request.method();
|
||||||
|
|
||||||
|
if (pathname === "/api/v1/dev/requests" && method === "GET") {
|
||||||
|
return json(route, { items: state.developerRequests ?? [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname === "/api/v1/dev/requests" && method === "POST") {
|
||||||
|
const payload = (request.postDataJSON() as {
|
||||||
|
organization?: string;
|
||||||
|
reason?: string;
|
||||||
|
}) || {};
|
||||||
|
const created: DeveloperRequest = {
|
||||||
|
id: `req-${Date.now()}`,
|
||||||
|
userId: "playwright-user",
|
||||||
|
userName: "Playwright User",
|
||||||
|
userEmail: "playwright@example.com",
|
||||||
|
organization: payload.organization ?? "Unknown",
|
||||||
|
reason: payload.reason ?? "No reason",
|
||||||
|
status: "pending",
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
if (!state.developerRequests) {
|
||||||
|
state.developerRequests = [];
|
||||||
|
}
|
||||||
|
state.developerRequests.push(created);
|
||||||
|
return json(route, created, 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathname === "/api/v1/dev/requests/status" && method === "GET") {
|
||||||
|
const myRequest = (state.developerRequests ?? []).find(
|
||||||
|
(r) => r.userId === "playwright-user",
|
||||||
|
);
|
||||||
|
return json(route, myRequest || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||||
|
pathname.endsWith("/approve") &&
|
||||||
|
method === "POST"
|
||||||
|
) {
|
||||||
|
const reqId = pathname.split("/")[5] ?? "";
|
||||||
|
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||||
|
if (!found) return json(route, { error: "not found" }, 404);
|
||||||
|
found.status = "approved";
|
||||||
|
found.approvedAt = new Date().toISOString();
|
||||||
|
return json(route, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||||
|
pathname.endsWith("/reject") &&
|
||||||
|
method === "POST"
|
||||||
|
) {
|
||||||
|
const reqId = pathname.split("/")[5] ?? "";
|
||||||
|
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||||
|
if (!found) return json(route, { error: "not found" }, 404);
|
||||||
|
found.status = "rejected";
|
||||||
|
found.rejectedAt = new Date().toISOString();
|
||||||
|
return json(route, found);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||||
|
pathname.endsWith("/cancel") &&
|
||||||
|
method === "POST"
|
||||||
|
) {
|
||||||
|
const reqId = pathname.split("/")[5] ?? "";
|
||||||
|
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||||
|
if (!found) return json(route, { error: "not found" }, 404);
|
||||||
|
found.status = "pending";
|
||||||
|
found.approvedAt = undefined;
|
||||||
|
return json(route, found);
|
||||||
|
}
|
||||||
|
|
||||||
if (pathname === "/api/v1/dev/my-tenants" && method === "GET") {
|
if (pathname === "/api/v1/dev/my-tenants" && method === "GET") {
|
||||||
return json(route, [
|
return json(route, [
|
||||||
{ id: "tenant-a", name: "Tenant A", slug: "tenant-a" },
|
{ id: "tenant-a", name: "Tenant A", slug: "tenant-a" },
|
||||||
|
|||||||
Reference in New Issue
Block a user