forked from baron/baron-sso
adminfront/devfront code-check 수정
This commit is contained in:
@@ -411,7 +411,7 @@ test.describe("User Management", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByRole("button", { name: /전역 Claim 저장|Save Global Claim/i })
|
.getByRole("button", { name: /사용자 Claim 값 저장/i })
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
|
|||||||
@@ -605,6 +605,10 @@ test.describe("Worksmobile tenant management", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const updateRowCheckbox = userComparisonSection
|
||||||
|
.getByRole("row", { name: /이업데이트/ })
|
||||||
|
.getByRole("checkbox");
|
||||||
|
await expect(updateRowCheckbox).not.toBeChecked();
|
||||||
await page
|
await page
|
||||||
.getByRole("row", { name: /이업데이트/ })
|
.getByRole("row", { name: /이업데이트/ })
|
||||||
.getByRole("checkbox")
|
.getByRole("checkbox")
|
||||||
|
|||||||
@@ -167,6 +167,20 @@ async function renderPage() {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForTextContent(container: HTMLElement, text: string) {
|
||||||
|
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||||
|
if (container.textContent?.includes(text)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Expected container text to include: ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
describe("ClientsPage", () => {
|
describe("ClientsPage", () => {
|
||||||
it("expands the list and applies search filters", async () => {
|
it("expands the list and applies search filters", async () => {
|
||||||
fetchClientsMock.mockResolvedValue({
|
fetchClientsMock.mockResolvedValue({
|
||||||
@@ -300,7 +314,7 @@ describe("ClientsPage", () => {
|
|||||||
fetchDeveloperRequestStatusMock.mockResolvedValue({ status: "none" });
|
fetchDeveloperRequestStatusMock.mockResolvedValue({ status: "none" });
|
||||||
|
|
||||||
const container = await renderPage();
|
const container = await renderPage();
|
||||||
expect(container.textContent).toContain("개발자 등록 신청하기");
|
await waitForTextContent(container, "개발자 등록 신청하기");
|
||||||
|
|
||||||
const requestButton = Array.from(container.querySelectorAll("button")).find(
|
const requestButton = Array.from(container.querySelectorAll("button")).find(
|
||||||
(button) => button.textContent === "개발자 등록 신청하기",
|
(button) => button.textContent === "개발자 등록 신청하기",
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
commonTableShellClass,
|
commonTableShellClass,
|
||||||
commonTableViewportClass,
|
commonTableViewportClass,
|
||||||
} from "../../../../common/ui/table";
|
} from "../../../../common/ui/table";
|
||||||
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
|
||||||
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
@@ -54,7 +53,6 @@ import { t } from "../../lib/i18n";
|
|||||||
import { resolveProfileRole } from "../../lib/role";
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
import { fetchMe } from "../auth/authApi";
|
import { fetchMe } from "../auth/authApi";
|
||||||
import { useDeveloperAccessGate } from "../developer-access/developerAccessGate";
|
|
||||||
import { resolveClientCreateAccess } from "./clientCreateAccess";
|
import { resolveClientCreateAccess } from "./clientCreateAccess";
|
||||||
import { ClientLogo } from "./components/ClientLogo";
|
import { ClientLogo } from "./components/ClientLogo";
|
||||||
|
|
||||||
@@ -103,19 +101,6 @@ function ClientsPage() {
|
|||||||
enabled: hasAccessToken,
|
enabled: hasAccessToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
|
||||||
hasDeveloperAccess: hasClientsPageAccess,
|
|
||||||
isDeveloperRequestPending,
|
|
||||||
canRequestDeveloperAccess,
|
|
||||||
isLoadingDeveloperAccessGate,
|
|
||||||
} = useDeveloperAccessGate({
|
|
||||||
hasAccessToken,
|
|
||||||
profileRole,
|
|
||||||
tenantId,
|
|
||||||
requiredPages: ["client_create"],
|
|
||||||
isLoadingIdentity: isLoadingMe,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createAccessState = resolveClientCreateAccess({
|
const createAccessState = resolveClientCreateAccess({
|
||||||
role: profileRole,
|
role: profileRole,
|
||||||
accessStatus: requestStatus,
|
accessStatus: requestStatus,
|
||||||
@@ -204,7 +189,6 @@ function ClientsPage() {
|
|||||||
const isLoading =
|
const isLoading =
|
||||||
isLoadingClients ||
|
isLoadingClients ||
|
||||||
isLoadingRequest ||
|
isLoadingRequest ||
|
||||||
isLoadingDeveloperAccessGate ||
|
|
||||||
(hasAccessToken && !profileRole && isLoadingMe);
|
(hasAccessToken && !profileRole && isLoadingMe);
|
||||||
|
|
||||||
const requestSort = (key: ClientSortKey) => {
|
const requestSort = (key: ClientSortKey) => {
|
||||||
@@ -219,41 +203,6 @@ function ClientsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasClientsPageAccess) {
|
|
||||||
return (
|
|
||||||
<div className="p-8">
|
|
||||||
<div className="mx-auto max-w-2xl">
|
|
||||||
<DeveloperAccessRequestCard
|
|
||||||
title={t("ui.dev.clients.registry.subtitle", "연동 앱")}
|
|
||||||
isPending={isDeveloperRequestPending}
|
|
||||||
canRequest={canRequestDeveloperAccess}
|
|
||||||
pendingMessage={t(
|
|
||||||
"msg.dev.dashboard.access_pending",
|
|
||||||
"개발자 권한 신청을 검토 중입니다.",
|
|
||||||
)}
|
|
||||||
deniedMessage={t(
|
|
||||||
"msg.dev.clients.access_denied",
|
|
||||||
"연동 앱 페이지에 접근할 권한이 없습니다.",
|
|
||||||
)}
|
|
||||||
pendingDetailMessage={t(
|
|
||||||
"msg.dev.dashboard.access_pending_detail",
|
|
||||||
"super admin이 승인하면 개요와 개발자 기능을 사용할 수 있습니다.",
|
|
||||||
)}
|
|
||||||
deniedDetailMessage={t(
|
|
||||||
"msg.dev.clients.access_denied_detail",
|
|
||||||
"개발자 권한 신청에서 개요 또는 연동 앱 추가 권한을 선택한 뒤 승인받아주세요.",
|
|
||||||
)}
|
|
||||||
actionLabel={t(
|
|
||||||
"ui.dev.welcome.btn_request",
|
|
||||||
"개발자 등록 신청하기",
|
|
||||||
)}
|
|
||||||
onAction={() => navigate("/developer-requests")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientError) {
|
if (clientError) {
|
||||||
const axiosError = clientError as AxiosError<{ error?: string }>;
|
const axiosError = clientError as AxiosError<{ error?: string }>;
|
||||||
if (axiosError.response?.status === 403) {
|
if (axiosError.response?.status === 403) {
|
||||||
@@ -289,7 +238,7 @@ function ClientsPage() {
|
|||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4" />
|
||||||
{t("ui.dev.clients.new", "새 클라이언트")}
|
{t("ui.dev.clients.new", "새 클라이언트")}
|
||||||
</Button>
|
</Button>
|
||||||
) : isDeveloperRequestPending ? (
|
) : isClientCreatePending ? (
|
||||||
<div className="flex items-center justify-end gap-3">
|
<div className="flex items-center justify-end gap-3">
|
||||||
<p className="max-w-xs text-right text-sm text-muted-foreground">
|
<p className="max-w-xs text-right text-sm text-muted-foreground">
|
||||||
{t(
|
{t(
|
||||||
|
|||||||
@@ -107,6 +107,22 @@ test.describe("DevFront role report", () => {
|
|||||||
const state = {
|
const state = {
|
||||||
clients: [makeClient("gitea-client", { name: "Gitea" })],
|
clients: [makeClient("gitea-client", { name: "Gitea" })],
|
||||||
consents: [] as Consent[],
|
consents: [] as Consent[],
|
||||||
|
developerRequests: [
|
||||||
|
{
|
||||||
|
id: "req-audit-approved",
|
||||||
|
userId: "playwright-user",
|
||||||
|
userName: "Playwright User",
|
||||||
|
name: "Playwright User",
|
||||||
|
userEmail: "playwright@example.com",
|
||||||
|
organization: "Tenant A",
|
||||||
|
reason: "Need access",
|
||||||
|
status: "approved",
|
||||||
|
accessPages: ["audit"],
|
||||||
|
createdAt: "2026-05-29T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-05-29T00:10:00.000Z",
|
||||||
|
approvedAt: "2026-05-29T00:10:00.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
auditLogs: [
|
auditLogs: [
|
||||||
{
|
{
|
||||||
event_id: "evt-rp-1",
|
event_id: "evt-rp-1",
|
||||||
|
|||||||
@@ -65,6 +65,22 @@ test.describe("DevFront security and isolation", () => {
|
|||||||
clients: [] as ReturnType<typeof makeClient>[],
|
clients: [] as ReturnType<typeof makeClient>[],
|
||||||
consents: [] as Consent[],
|
consents: [] as Consent[],
|
||||||
auditLogsByCursor: undefined,
|
auditLogsByCursor: undefined,
|
||||||
|
developerRequests: [
|
||||||
|
{
|
||||||
|
id: "req-audit-approved",
|
||||||
|
userId: "playwright-user",
|
||||||
|
userName: "Playwright User",
|
||||||
|
name: "Playwright User",
|
||||||
|
userEmail: "playwright@example.com",
|
||||||
|
organization: "Tenant A",
|
||||||
|
reason: "Need access",
|
||||||
|
status: "approved",
|
||||||
|
accessPages: ["audit"],
|
||||||
|
createdAt: "2026-05-29T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-05-29T00:10:00.000Z",
|
||||||
|
approvedAt: "2026-05-29T00:10:00.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
await installDevApiMock(page, state);
|
await installDevApiMock(page, state);
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,30 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
|||||||
const readMockRole = () =>
|
const readMockRole = () =>
|
||||||
(state.mockRole ?? seededRoles.get(page) ?? "super_admin").trim();
|
(state.mockRole ?? seededRoles.get(page) ?? "super_admin").trim();
|
||||||
|
|
||||||
|
const buildDeveloperAccessStatus = () => {
|
||||||
|
const requests = state.developerRequests ?? [];
|
||||||
|
const myRequests = requests.filter((request) => request.userId === "playwright-user");
|
||||||
|
const approvedPages = myRequests
|
||||||
|
.filter((request) => request.status === "approved")
|
||||||
|
.flatMap((request) => request.accessPages ?? ["all"]);
|
||||||
|
const pendingPages = myRequests
|
||||||
|
.filter((request) => request.status === "pending")
|
||||||
|
.flatMap((request) => request.accessPages ?? ["all"]);
|
||||||
|
|
||||||
|
const latestRequest = myRequests[myRequests.length - 1];
|
||||||
|
if (!latestRequest) {
|
||||||
|
return {
|
||||||
|
status: "none" as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: latestRequest.status,
|
||||||
|
approvedPages,
|
||||||
|
pendingPages,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const buildSelfConfigEditorRelation = (): ClientRelation => ({
|
const buildSelfConfigEditorRelation = (): ClientRelation => ({
|
||||||
relation: "config_editor",
|
relation: "config_editor",
|
||||||
subject: "User:playwright-user",
|
subject: "User:playwright-user",
|
||||||
@@ -358,10 +382,7 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
|||||||
pathname === "/api/v1/dev/developer-request/status") &&
|
pathname === "/api/v1/dev/developer-request/status") &&
|
||||||
method === "GET"
|
method === "GET"
|
||||||
) {
|
) {
|
||||||
const myRequest = (state.developerRequests ?? []).find(
|
return json(route, buildDeveloperAccessStatus());
|
||||||
(r) => r.userId === "playwright-user",
|
|
||||||
);
|
|
||||||
return json(route, myRequest || null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
Reference in New Issue
Block a user