forked from baron/baron-sso
custom claim 권한체크 확인
This commit is contained in:
232
devfront/src/features/clients/ClientGeneralPage.claims.test.tsx
Normal file
232
devfront/src/features/clients/ClientGeneralPage.claims.test.tsx
Normal file
@@ -0,0 +1,232 @@
|
||||
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 type { ClientDetailResponse } from "../../lib/devApi";
|
||||
import ClientGeneralPage from "./ClientGeneralPage";
|
||||
|
||||
const navigateMock = vi.fn();
|
||||
const fetchClientMock = vi.fn();
|
||||
const updateClientMock = vi.fn();
|
||||
const fetchClientRelationsMock = vi.fn();
|
||||
const fetchMyTenantsMock = vi.fn();
|
||||
const fetchMeMock = vi.fn();
|
||||
|
||||
let authState = {
|
||||
user: {
|
||||
access_token: "access-token",
|
||||
profile: {
|
||||
sub: "admin-user",
|
||||
role: "super_admin",
|
||||
name: "Dev Admin",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
vi.mock("react-oidc-context", () => ({
|
||||
useAuth: () => authState,
|
||||
}));
|
||||
|
||||
vi.mock("react-router-dom", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("react-router-dom")>(
|
||||
"react-router-dom",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
useNavigate: () => navigateMock,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../lib/devApi", () => ({
|
||||
createClient: vi.fn(),
|
||||
deleteClient: vi.fn(),
|
||||
fetchClient: (...args: unknown[]) => fetchClientMock(...args),
|
||||
fetchClientRelations: (...args: unknown[]) =>
|
||||
fetchClientRelationsMock(...args),
|
||||
fetchMyTenants: (...args: unknown[]) => fetchMyTenantsMock(...args),
|
||||
refreshHeadlessJwksCache: vi.fn(),
|
||||
revokeHeadlessJwksCache: vi.fn(),
|
||||
updateClient: (...args: unknown[]) => updateClientMock(...args),
|
||||
updateClientStatus: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../auth/authApi", () => ({
|
||||
fetchMe: (...args: unknown[]) => fetchMeMock(...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[] = [];
|
||||
|
||||
function makeClientDetail(claimKey: string): ClientDetailResponse {
|
||||
return {
|
||||
client: {
|
||||
id: "client-claims",
|
||||
name: "Claims App",
|
||||
type: "private",
|
||||
status: "active",
|
||||
redirectUris: ["https://rp.example.com/callback"],
|
||||
scopes: ["openid", "profile"],
|
||||
tokenEndpointAuthMethod: "client_secret_basic",
|
||||
metadata: {
|
||||
description: "Claims app",
|
||||
structured_scopes: [
|
||||
{
|
||||
id: "1",
|
||||
name: "openid",
|
||||
description: "",
|
||||
mandatory: true,
|
||||
},
|
||||
],
|
||||
id_token_claims: [
|
||||
{
|
||||
namespace: "rp_claims",
|
||||
key: claimKey,
|
||||
value: "A",
|
||||
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",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function flush() {
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
}
|
||||
|
||||
async function setInputValue(input: HTMLInputElement, value: string) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(
|
||||
HTMLInputElement.prototype,
|
||||
"value",
|
||||
);
|
||||
descriptor?.set?.call(input, value);
|
||||
input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
await flush();
|
||||
}
|
||||
|
||||
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-claims/settings"]}>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/clients/:id/settings"
|
||||
element={<ClientGeneralPage />}
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flush();
|
||||
|
||||
return { container, queryClient };
|
||||
}
|
||||
|
||||
describe("ClientGeneralPage RP claims", () => {
|
||||
beforeEach(() => {
|
||||
authState = {
|
||||
user: {
|
||||
access_token: "access-token",
|
||||
profile: {
|
||||
sub: "admin-user",
|
||||
role: "super_admin",
|
||||
name: "Dev Admin",
|
||||
},
|
||||
},
|
||||
};
|
||||
fetchClientMock.mockResolvedValue(makeClientDetail("old_claim"));
|
||||
updateClientMock.mockResolvedValue(makeClientDetail("new_claim"));
|
||||
fetchClientRelationsMock.mockResolvedValue({ items: [] });
|
||||
fetchMyTenantsMock.mockResolvedValue([]);
|
||||
fetchMeMock.mockResolvedValue({
|
||||
id: "admin-user",
|
||||
role: "super_admin",
|
||||
name: "Dev Admin",
|
||||
});
|
||||
navigateMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) {
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
}
|
||||
vi.clearAllMocks();
|
||||
document.body.innerHTML = "";
|
||||
});
|
||||
|
||||
it("updates the client detail cache with saved RP claims before stale data can rehydrate the form", async () => {
|
||||
const { container, queryClient } = await renderPage();
|
||||
|
||||
const claimKeyInput = container.querySelector<HTMLInputElement>(
|
||||
'input[placeholder="e.g. locale"]',
|
||||
);
|
||||
expect(claimKeyInput).not.toBeNull();
|
||||
expect(claimKeyInput?.value).toBe("old_claim");
|
||||
|
||||
await setInputValue(claimKeyInput as HTMLInputElement, "new_claim");
|
||||
|
||||
const saveButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) =>
|
||||
button.textContent?.includes("저장") ||
|
||||
button.textContent?.includes("Save"),
|
||||
);
|
||||
expect(saveButton).toBeDefined();
|
||||
|
||||
await act(async () => {
|
||||
saveButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
await flush();
|
||||
|
||||
const cached = queryClient.getQueryData<ClientDetailResponse>([
|
||||
"client",
|
||||
"client-claims",
|
||||
]);
|
||||
expect(cached?.client.metadata?.id_token_claims).toEqual([
|
||||
{
|
||||
namespace: "rp_claims",
|
||||
key: "new_claim",
|
||||
value: "A",
|
||||
valueType: "text",
|
||||
readPermission: "admin_only",
|
||||
writePermission: "admin_only",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user