forked from baron/baron-sso
RP Custom Claims 권한 체크박스 정리
This commit is contained in:
204
devfront/src/features/clients/ClientConsentsPage.test.tsx
Normal file
204
devfront/src/features/clients/ClientConsentsPage.test.tsx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { act } from "react";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import ClientConsentsPage from "./ClientConsentsPage";
|
||||||
|
|
||||||
|
const fetchClientMock = vi.fn();
|
||||||
|
const fetchConsentsMock = vi.fn();
|
||||||
|
const fetchRPUserMetadataMock = vi.fn();
|
||||||
|
const updateRPUserMetadataMock = vi.fn();
|
||||||
|
const revokeConsentMock = vi.fn();
|
||||||
|
|
||||||
|
vi.mock("../../lib/devApi", () => ({
|
||||||
|
fetchClient: (...args: unknown[]) => fetchClientMock(...args),
|
||||||
|
fetchConsents: (...args: unknown[]) => fetchConsentsMock(...args),
|
||||||
|
fetchRPUserMetadata: (...args: unknown[]) => fetchRPUserMetadataMock(...args),
|
||||||
|
updateRPUserMetadata: (...args: unknown[]) =>
|
||||||
|
updateRPUserMetadataMock(...args),
|
||||||
|
revokeConsent: (...args: unknown[]) => revokeConsentMock(...args),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../lib/i18n", () => ({
|
||||||
|
t: (key: string, fallback?: string, vars?: Record<string, unknown>) => {
|
||||||
|
let text = fallback ?? key;
|
||||||
|
for (const [name, value] of Object.entries(vars ?? {})) {
|
||||||
|
text = text.replaceAll(`{{${name}}}`, String(value));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const roots: Root[] = [];
|
||||||
|
|
||||||
|
const clientDetail = {
|
||||||
|
client: {
|
||||||
|
id: "client-a",
|
||||||
|
name: "Claims App",
|
||||||
|
type: "private" as const,
|
||||||
|
status: "active" as const,
|
||||||
|
redirectUris: ["https://rp.example.com/callback"],
|
||||||
|
scopes: ["openid", "profile"],
|
||||||
|
tokenEndpointAuthMethod: "client_secret_basic",
|
||||||
|
metadata: {
|
||||||
|
id_token_claims: [
|
||||||
|
{
|
||||||
|
namespace: "rp_claims",
|
||||||
|
key: "license",
|
||||||
|
value: "12345678",
|
||||||
|
valueType: "text",
|
||||||
|
readPermission: "admin_only",
|
||||||
|
writePermission: "admin_only",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
discovery: "https://issuer/.well-known/openid-configuration",
|
||||||
|
issuer: "https://issuer",
|
||||||
|
authorization: "https://issuer/oauth2/auth",
|
||||||
|
token: "https://issuer/oauth2/token",
|
||||||
|
userinfo: "https://issuer/userinfo",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildMetadata() {
|
||||||
|
return {
|
||||||
|
license: "abcd",
|
||||||
|
license_permissions: {
|
||||||
|
readPermission: "user_and_admin",
|
||||||
|
writePermission: "admin_only",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flush() {
|
||||||
|
await act(async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderPage() {
|
||||||
|
const container = document.createElement("div");
|
||||||
|
document.body.appendChild(container);
|
||||||
|
const root = createRoot(container);
|
||||||
|
roots.push(root);
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: { retry: false },
|
||||||
|
mutations: { retry: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<MemoryRouter initialEntries={["/clients/client-a/consents"]}>
|
||||||
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path="/clients/:id/consents"
|
||||||
|
element={<ClientConsentsPage />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
</QueryClientProvider>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
return { container };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("ClientConsentsPage RP custom claims", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchClientMock.mockResolvedValue(clientDetail);
|
||||||
|
fetchConsentsMock.mockResolvedValue({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
subject: "user-1",
|
||||||
|
userName: "Consent User",
|
||||||
|
clientId: "client-a",
|
||||||
|
clientName: "Claims App",
|
||||||
|
grantedScopes: ["openid", "profile"],
|
||||||
|
authenticatedAt: "2026-06-11T09:00:00Z",
|
||||||
|
createdAt: "2026-06-10T09:00:00Z",
|
||||||
|
status: "active",
|
||||||
|
tenantId: "tenant-1",
|
||||||
|
tenantName: "Hanmac",
|
||||||
|
rpMetadata: buildMetadata(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
fetchRPUserMetadataMock.mockResolvedValue({
|
||||||
|
clientId: "client-a",
|
||||||
|
userId: "user-1",
|
||||||
|
metadata: buildMetadata(),
|
||||||
|
});
|
||||||
|
updateRPUserMetadataMock.mockResolvedValue({
|
||||||
|
clientId: "client-a",
|
||||||
|
userId: "user-1",
|
||||||
|
metadata: buildMetadata(),
|
||||||
|
});
|
||||||
|
revokeConsentMock.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const root of roots.splice(0)) {
|
||||||
|
act(() => {
|
||||||
|
root.unmount();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
vi.clearAllMocks();
|
||||||
|
document.body.innerHTML = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("removes the RP custom claim permission selectors while keeping claim data editable", async () => {
|
||||||
|
const { container } = await renderPage();
|
||||||
|
|
||||||
|
const editButton = Array.from(container.querySelectorAll("button")).find(
|
||||||
|
(button) =>
|
||||||
|
button.textContent?.includes("사용자 Claim 설정") ||
|
||||||
|
button.textContent?.includes("User Claim Settings"),
|
||||||
|
);
|
||||||
|
expect(editButton).toBeDefined();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
editButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
});
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
container.querySelectorAll('select[aria-label="읽기 권한"]'),
|
||||||
|
).toHaveLength(0);
|
||||||
|
expect(
|
||||||
|
container.querySelectorAll('select[aria-label="쓰기 권한"]'),
|
||||||
|
).toHaveLength(0);
|
||||||
|
expect(container.textContent).toContain("license");
|
||||||
|
expect(container.textContent).toContain("abcd");
|
||||||
|
|
||||||
|
const saveButton = Array.from(container.querySelectorAll("button")).find(
|
||||||
|
(button) =>
|
||||||
|
button.textContent?.includes("Claim 저장") ||
|
||||||
|
button.textContent?.includes("Save"),
|
||||||
|
);
|
||||||
|
expect(saveButton).toBeDefined();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
});
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(updateRPUserMetadataMock).toHaveBeenCalledWith(
|
||||||
|
"client-a",
|
||||||
|
"user-1",
|
||||||
|
expect.objectContaining({
|
||||||
|
license: "abcd",
|
||||||
|
license_permissions: {
|
||||||
|
readPermission: "user_and_admin",
|
||||||
|
writePermission: "admin_only",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1023,7 +1023,7 @@ function ClientConsentsPage() {
|
|||||||
metadataDraftRows.map((row) => (
|
metadataDraftRows.map((row) => (
|
||||||
<div
|
<div
|
||||||
key={row.id}
|
key={row.id}
|
||||||
className="grid gap-3 md:grid-cols-[minmax(180px,0.8fr)_minmax(220px,1fr)_150px_150px_auto]"
|
className="grid gap-3 md:items-center md:grid-cols-[180px_minmax(220px,320px)_88px]"
|
||||||
>
|
>
|
||||||
<div className="flex h-10 items-center rounded-md border bg-muted/30 px-3 font-mono text-xs">
|
<div className="flex h-10 items-center rounded-md border bg-muted/30 px-3 font-mono text-xs">
|
||||||
{row.key}
|
{row.key}
|
||||||
@@ -1036,7 +1036,7 @@ function ClientConsentsPage() {
|
|||||||
value: event.target.value,
|
value: event.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="h-10 rounded-md border border-input bg-background px-3 font-mono text-xs shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
className="h-10 w-full max-w-[180px] rounded-md border border-input bg-background px-3 font-mono text-xs shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||||
aria-label={`${row.key} boolean`}
|
aria-label={`${row.key} boolean`}
|
||||||
>
|
>
|
||||||
<option value="true">true</option>
|
<option value="true">true</option>
|
||||||
@@ -1051,12 +1051,12 @@ function ClientConsentsPage() {
|
|||||||
value: event.target.value,
|
value: event.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="min-h-10 font-mono text-xs"
|
|
||||||
placeholder={
|
placeholder={
|
||||||
row.valueType === "array"
|
row.valueType === "array"
|
||||||
? `["value"]`
|
? `["value"]`
|
||||||
: `{"key": "value"}`
|
: `{"key": "value"}`
|
||||||
}
|
}
|
||||||
|
className="min-h-10 w-full max-w-[320px] font-mono text-xs"
|
||||||
aria-label={`${row.key} ${row.valueType}`}
|
aria-label={`${row.key} ${row.valueType}`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -1071,7 +1071,7 @@ function ClientConsentsPage() {
|
|||||||
value: event.target.value,
|
value: event.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="font-mono text-xs"
|
className="w-full max-w-[320px] font-mono text-xs"
|
||||||
placeholder={t(
|
placeholder={t(
|
||||||
"ui.dev.clients.consents.rp_claims.value_placeholder",
|
"ui.dev.clients.consents.rp_claims.value_placeholder",
|
||||||
"claim value",
|
"claim value",
|
||||||
@@ -1099,63 +1099,9 @@ function ClientConsentsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<select
|
|
||||||
value={row.readPermission}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateMetadataDraftRow(row.id, {
|
|
||||||
readPermission: event.target
|
|
||||||
.value as CustomClaimPermission,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="h-10 rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
||||||
aria-label={t(
|
|
||||||
"ui.dev.clients.consents.rp_claims.read_permission",
|
|
||||||
"읽기 권한",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<option value="admin_only">
|
|
||||||
{t(
|
|
||||||
"ui.common.custom_claim_permission.admin_only",
|
|
||||||
"관리자만 가능",
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
<option value="user_and_admin">
|
|
||||||
{t(
|
|
||||||
"ui.common.custom_claim_permission.user_and_admin",
|
|
||||||
"사용자 및 관리자 가능",
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<select
|
|
||||||
value={row.writePermission}
|
|
||||||
onChange={(event) =>
|
|
||||||
updateMetadataDraftRow(row.id, {
|
|
||||||
writePermission: event.target
|
|
||||||
.value as CustomClaimPermission,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="h-10 rounded-md border border-input bg-background px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
||||||
aria-label={t(
|
|
||||||
"ui.dev.clients.consents.rp_claims.write_permission",
|
|
||||||
"쓰기 권한",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<option value="admin_only">
|
|
||||||
{t(
|
|
||||||
"ui.common.custom_claim_permission.admin_only",
|
|
||||||
"관리자만 가능",
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
<option value="user_and_admin">
|
|
||||||
{t(
|
|
||||||
"ui.common.custom_claim_permission.user_and_admin",
|
|
||||||
"사용자 및 관리자 가능",
|
|
||||||
)}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<Badge
|
<Badge
|
||||||
variant="muted"
|
variant="muted"
|
||||||
className="h-10 justify-center rounded-md px-3 font-mono text-xs"
|
className="h-10 w-fit justify-center rounded-md px-3 font-mono text-xs"
|
||||||
>
|
>
|
||||||
{row.valueType}
|
{row.valueType}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
Reference in New Issue
Block a user