1
0
forked from baron/baron-sso

custom claim 권한체크 확인

This commit is contained in:
2026-06-11 08:29:25 +09:00
parent 839ca9d407
commit 4d77060b5d
79 changed files with 4268 additions and 670 deletions

View 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",
},
]);
});
});