forked from baron/baron-sso
dev 병합 code check 수정
This commit is contained in:
@@ -124,7 +124,12 @@ export default function DeveloperRequestPage() {
|
||||
|
||||
const handleCancelApproval = (id: number) => {
|
||||
if (!adminNotes[id]) {
|
||||
alert(t("msg.dev.request.need_cancel_notes", "승인 취소 사유를 입력해주세요."));
|
||||
alert(
|
||||
t(
|
||||
"msg.dev.request.need_cancel_notes",
|
||||
"승인 취소 사유를 입력해주세요.",
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
cancelApprovalMutation.mutate({ id, adminNotes: adminNotes[id] });
|
||||
@@ -184,14 +189,20 @@ export default function DeveloperRequestPage() {
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{isSuperAdmin && (
|
||||
<TableHead>{t("ui.dev.request.table.user", "사용자")}</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.user", "사용자")}
|
||||
</TableHead>
|
||||
)}
|
||||
<TableHead>{t("ui.dev.request.table.org", "소속")}</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.reason", "신청 사유")}
|
||||
</TableHead>
|
||||
<TableHead>{t("ui.dev.request.table.status", "상태")}</TableHead>
|
||||
<TableHead>{t("ui.dev.request.table.date", "신청일")}</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.status", "상태")}
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
{t("ui.dev.request.table.date", "신청일")}
|
||||
</TableHead>
|
||||
{isSuperAdmin && (
|
||||
<TableHead className="text-right">
|
||||
{t("ui.dev.request.table.actions", "관리")}
|
||||
@@ -312,7 +323,10 @@ export default function DeveloperRequestPage() {
|
||||
) : (
|
||||
<span className="text-muted-foreground text-xs italic">
|
||||
{req.status === "cancelled"
|
||||
? t("ui.dev.request.status.cancelled", "승인 취소됨")
|
||||
? t(
|
||||
"ui.dev.request.status.cancelled",
|
||||
"승인 취소됨",
|
||||
)
|
||||
: t("ui.common.rejected", "반려됨")}
|
||||
</span>
|
||||
)}
|
||||
@@ -379,7 +393,6 @@ function StatusBadge({ status }: { status: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface RequestAccessModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -444,10 +444,7 @@ export async function fetchDeveloperRequests(status?: string) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function approveDeveloperRequest(
|
||||
id: number,
|
||||
adminNotes: string,
|
||||
) {
|
||||
export async function approveDeveloperRequest(id: number, adminNotes: string) {
|
||||
const { data } = await apiClient.post<{ status: string }>(
|
||||
`/dev/developer-request/${id}/approve`,
|
||||
{ adminNotes },
|
||||
@@ -455,10 +452,7 @@ export async function approveDeveloperRequest(
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function rejectDeveloperRequest(
|
||||
id: number,
|
||||
adminNotes: string,
|
||||
) {
|
||||
export async function rejectDeveloperRequest(id: number, adminNotes: string) {
|
||||
const { data } = await apiClient.post<{ status: string }>(
|
||||
`/dev/developer-request/${id}/reject`,
|
||||
{ adminNotes },
|
||||
|
||||
@@ -325,6 +325,51 @@ loaded_count = ""
|
||||
loading = ""
|
||||
subtitle = ""
|
||||
|
||||
[msg.dev.request]
|
||||
admin_desc = ""
|
||||
approved = ""
|
||||
cancelled = ""
|
||||
empty = ""
|
||||
need_cancel_notes = ""
|
||||
need_notes = ""
|
||||
rejected = ""
|
||||
user_desc = ""
|
||||
|
||||
[msg.dev.request.modal]
|
||||
desc = ""
|
||||
email = ""
|
||||
name = ""
|
||||
org = ""
|
||||
phone = ""
|
||||
reason = ""
|
||||
reason_placeholder = ""
|
||||
role = ""
|
||||
title = ""
|
||||
|
||||
[msg.dev.request.status]
|
||||
approved = ""
|
||||
cancelled = ""
|
||||
pending = ""
|
||||
rejected = ""
|
||||
|
||||
[msg.dev.request.table]
|
||||
actions = ""
|
||||
date = ""
|
||||
org = ""
|
||||
reason = ""
|
||||
status = ""
|
||||
user = ""
|
||||
|
||||
[msg.dev.request.list]
|
||||
title = ""
|
||||
|
||||
[msg.dev.request.admin]
|
||||
notes_placeholder = ""
|
||||
|
||||
[msg.dev.request.cancel]
|
||||
approval = ""
|
||||
notes_placeholder = ""
|
||||
|
||||
[msg.dev.clients]
|
||||
load_error = ""
|
||||
loading = ""
|
||||
@@ -1290,6 +1335,42 @@ scope_badge = ""
|
||||
audit_logs = ""
|
||||
clients = ""
|
||||
logout = ""
|
||||
developer_request = ""
|
||||
|
||||
[ui.dev.welcome]
|
||||
btn_request = ""
|
||||
|
||||
[ui.dev.request]
|
||||
admin_notes_placeholder = ""
|
||||
cancel_approval = ""
|
||||
cancel_notes_placeholder = ""
|
||||
|
||||
[ui.dev.request.list]
|
||||
title = ""
|
||||
|
||||
[ui.dev.request.modal]
|
||||
email = ""
|
||||
name = ""
|
||||
org = ""
|
||||
phone = ""
|
||||
reason = ""
|
||||
reason_placeholder = ""
|
||||
role = ""
|
||||
title = ""
|
||||
|
||||
[ui.dev.request.status]
|
||||
approved = ""
|
||||
cancelled = ""
|
||||
pending = ""
|
||||
rejected = ""
|
||||
|
||||
[ui.dev.request.table]
|
||||
actions = ""
|
||||
date = ""
|
||||
org = ""
|
||||
reason = ""
|
||||
status = ""
|
||||
user = ""
|
||||
|
||||
[ui.dev.audit]
|
||||
load_more = ""
|
||||
|
||||
@@ -19,39 +19,50 @@ test.describe("DevFront developer request and management", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("user can request developer access when no RP exists", async ({ page }) => {
|
||||
test("user can request developer access when no RP exists", async ({
|
||||
page,
|
||||
}) => {
|
||||
const state = {
|
||||
clients: [],
|
||||
consents: [],
|
||||
developerRequests: [],
|
||||
};
|
||||
await seedAuth(page, "user");
|
||||
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' });
|
||||
// Click request link and open the request modal on the dedicated page
|
||||
const requestBtn = page.getByRole("button", {
|
||||
name: /개발자 등록 신청하기|개발자 등록 신청/,
|
||||
});
|
||||
await requestBtn.waitFor({ state: "visible" });
|
||||
await requestBtn.click();
|
||||
await expect(page).toHaveURL(/\/developer-requests$/);
|
||||
|
||||
// Fill Form (using direct selectors for reliability)
|
||||
await page.locator("#org").fill("QA Team");
|
||||
const openRequestBtn = page.getByRole("button", {
|
||||
name: /신규 신청하기|Request|Apply/,
|
||||
});
|
||||
await openRequestBtn.click();
|
||||
|
||||
// Fill Form (organization is read-only and comes from the active tenant)
|
||||
await page.locator("#reason").fill("Need to test OIDC integration");
|
||||
|
||||
|
||||
// Submit
|
||||
await page.getByRole('button', { name: /신청하기|Submit/ }).click();
|
||||
await page.getByRole("button", { name: "신청하기", exact: true }).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 }) => {
|
||||
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",
|
||||
name: "Requester User",
|
||||
userEmail: "user1@example.com",
|
||||
organization: "Dev Team",
|
||||
reason: "API Test",
|
||||
@@ -72,26 +83,30 @@ test.describe("DevFront developer request and management", () => {
|
||||
await page.goto("/developer-requests");
|
||||
|
||||
// Wait for data to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
await expect(page.locator("table")).toContainText("Requester User", { timeout: 10000 });
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(page.locator("table")).toContainText("Requester User", {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Approve
|
||||
const approveBtn = page.getByRole('button', { name: '승인' }).first();
|
||||
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 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 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 }) => {
|
||||
test("approved user can see 'Add App' guidance and create RP", async ({
|
||||
page,
|
||||
}) => {
|
||||
const request: DeveloperRequest = {
|
||||
id: "req-approved",
|
||||
userId: "playwright-user",
|
||||
@@ -112,33 +127,41 @@ test.describe("DevFront developer request and management", () => {
|
||||
developerRequests: [request],
|
||||
};
|
||||
|
||||
await seedAuth(page, "rp_admin");
|
||||
await seedAuth(page, "rp_admin");
|
||||
await installDevApiMock(page, state);
|
||||
|
||||
await page.goto("/clients");
|
||||
|
||||
|
||||
// Click Add App
|
||||
const createBtn = page.getByRole('button', { name: /연동 앱 추가/ }).first();
|
||||
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']");
|
||||
|
||||
const nameInput = page.getByPlaceholder(
|
||||
/My Awesome Application|예: 멋진 애플리케이션/,
|
||||
);
|
||||
await nameInput.fill("E2E Test RP");
|
||||
await nameInput.press('Tab');
|
||||
await nameInput.press("Tab");
|
||||
|
||||
const uriInput = page.locator("textarea.font-mono");
|
||||
await uriInput.fill("https://example.com/callback");
|
||||
await uriInput.press('Tab');
|
||||
|
||||
await uriInput.press("Tab");
|
||||
|
||||
// Submit
|
||||
const submitBtn = page.getByRole('button', { name: /생성/ }).filter({ hasNotText: '취소' });
|
||||
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/);
|
||||
await expect(
|
||||
page.getByRole("heading", { name: /연동 앱 설정|Settings/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -261,19 +261,30 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
const { pathname, searchParams } = url;
|
||||
const method = request.method();
|
||||
|
||||
if (pathname === "/api/v1/dev/requests" && method === "GET") {
|
||||
return json(route, { items: state.developerRequests ?? [] });
|
||||
if (
|
||||
(pathname === "/api/v1/dev/requests" ||
|
||||
pathname === "/api/v1/dev/developer-request/list") &&
|
||||
method === "GET"
|
||||
) {
|
||||
return json(route, state.developerRequests ?? []);
|
||||
}
|
||||
|
||||
if (pathname === "/api/v1/dev/requests" && method === "POST") {
|
||||
const payload = (request.postDataJSON() as {
|
||||
organization?: string;
|
||||
reason?: string;
|
||||
}) || {};
|
||||
if (
|
||||
(pathname === "/api/v1/dev/requests" ||
|
||||
pathname === "/api/v1/dev/developer-request") &&
|
||||
method === "POST"
|
||||
) {
|
||||
const payload =
|
||||
(request.postDataJSON() as {
|
||||
name?: string;
|
||||
organization?: string;
|
||||
reason?: string;
|
||||
}) || {};
|
||||
const created: DeveloperRequest = {
|
||||
id: `req-${Date.now()}`,
|
||||
userId: "playwright-user",
|
||||
userName: "Playwright User",
|
||||
userName: payload.name ?? "Playwright User",
|
||||
name: payload.name ?? "Playwright User",
|
||||
userEmail: "playwright@example.com",
|
||||
organization: payload.organization ?? "Unknown",
|
||||
reason: payload.reason ?? "No reason",
|
||||
@@ -288,7 +299,11 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
return json(route, created, 201);
|
||||
}
|
||||
|
||||
if (pathname === "/api/v1/dev/requests/status" && method === "GET") {
|
||||
if (
|
||||
(pathname === "/api/v1/dev/requests/status" ||
|
||||
pathname === "/api/v1/dev/developer-request/status") &&
|
||||
method === "GET"
|
||||
) {
|
||||
const myRequest = (state.developerRequests ?? []).find(
|
||||
(r) => r.userId === "playwright-user",
|
||||
);
|
||||
@@ -296,11 +311,12 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
}
|
||||
|
||||
if (
|
||||
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||
(pathname.startsWith("/api/v1/dev/requests/") ||
|
||||
pathname.startsWith("/api/v1/dev/developer-request/")) &&
|
||||
pathname.endsWith("/approve") &&
|
||||
method === "POST"
|
||||
) {
|
||||
const reqId = pathname.split("/")[5] ?? "";
|
||||
const reqId = pathname.split("/")[5] ?? pathname.split("/")[4] ?? "";
|
||||
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||
if (!found) return json(route, { error: "not found" }, 404);
|
||||
found.status = "approved";
|
||||
@@ -309,11 +325,12 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
}
|
||||
|
||||
if (
|
||||
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||
(pathname.startsWith("/api/v1/dev/requests/") ||
|
||||
pathname.startsWith("/api/v1/dev/developer-request/")) &&
|
||||
pathname.endsWith("/reject") &&
|
||||
method === "POST"
|
||||
) {
|
||||
const reqId = pathname.split("/")[5] ?? "";
|
||||
const reqId = pathname.split("/")[5] ?? pathname.split("/")[4] ?? "";
|
||||
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||
if (!found) return json(route, { error: "not found" }, 404);
|
||||
found.status = "rejected";
|
||||
@@ -322,11 +339,12 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
}
|
||||
|
||||
if (
|
||||
pathname.startsWith("/api/v1/dev/requests/") &&
|
||||
pathname.endsWith("/cancel") &&
|
||||
(pathname.startsWith("/api/v1/dev/requests/") ||
|
||||
pathname.startsWith("/api/v1/dev/developer-request/")) &&
|
||||
(pathname.endsWith("/cancel") || pathname.endsWith("/cancel-approval")) &&
|
||||
method === "POST"
|
||||
) {
|
||||
const reqId = pathname.split("/")[5] ?? "";
|
||||
const reqId = pathname.split("/")[5] ?? pathname.split("/")[4] ?? "";
|
||||
const found = state.developerRequests?.find((r) => r.id === reqId);
|
||||
if (!found) return json(route, { error: "not found" }, 404);
|
||||
found.status = "pending";
|
||||
|
||||
Reference in New Issue
Block a user