forked from baron/baron-sso
218 lines
5.9 KiB
TypeScript
218 lines
5.9 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { act } from "react-dom/test-utils";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import DeveloperRequestPage from "./DeveloperRequestPage";
|
|
|
|
const fetchDeveloperRequestsMock = vi.fn();
|
|
const fetchMyTenantsMock = vi.fn();
|
|
const fetchMeMock = vi.fn();
|
|
const requestDeveloperAccessMock = vi.fn();
|
|
|
|
let authState = {
|
|
user: {
|
|
access_token: "access-token",
|
|
profile: {
|
|
role: "user",
|
|
tenant_id: "tenant-1",
|
|
companyCode: "HANMAC",
|
|
name: "Requester",
|
|
email: "requester@example.com",
|
|
phone: "010-1234-5678",
|
|
},
|
|
},
|
|
};
|
|
|
|
vi.mock("react-oidc-context", () => ({
|
|
useAuth: () => authState,
|
|
}));
|
|
|
|
vi.mock("../auth/authApi", () => ({
|
|
fetchMe: () => fetchMeMock(),
|
|
}));
|
|
|
|
vi.mock("../../lib/devApi", () => ({
|
|
fetchDeveloperRequests: () => fetchDeveloperRequestsMock(),
|
|
fetchMyTenants: () => fetchMyTenantsMock(),
|
|
requestDeveloperAccess: (...args: unknown[]) =>
|
|
requestDeveloperAccessMock(...args),
|
|
approveDeveloperRequest: vi.fn(),
|
|
rejectDeveloperRequest: vi.fn(),
|
|
cancelDeveloperRequestApproval: vi.fn(),
|
|
}));
|
|
|
|
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[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) {
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
}
|
|
vi.clearAllMocks();
|
|
document.body.innerHTML = "";
|
|
});
|
|
|
|
beforeEach(() => {
|
|
authState = {
|
|
user: {
|
|
access_token: "access-token",
|
|
profile: {
|
|
role: "user",
|
|
tenant_id: "tenant-1",
|
|
companyCode: "HANMAC",
|
|
name: "Requester",
|
|
email: "requester@example.com",
|
|
phone: "010-1234-5678",
|
|
},
|
|
},
|
|
};
|
|
|
|
fetchDeveloperRequestsMock.mockResolvedValue([]);
|
|
fetchMyTenantsMock.mockResolvedValue([
|
|
{
|
|
id: "tenant-1",
|
|
name: "Hanmac",
|
|
slug: "hanmac",
|
|
type: "COMPANY",
|
|
parentId: null,
|
|
description: "",
|
|
status: "active",
|
|
memberCount: 10,
|
|
createdAt: "2026-05-01T00:00:00Z",
|
|
updatedAt: "2026-05-01T00:00:00Z",
|
|
},
|
|
]);
|
|
fetchMeMock.mockResolvedValue({
|
|
id: "user-1",
|
|
name: "Requester",
|
|
email: "requester@example.com",
|
|
phone: "010-1234-5678",
|
|
role: "user",
|
|
});
|
|
requestDeveloperAccessMock.mockResolvedValue({ status: "pending" });
|
|
});
|
|
|
|
async function setTextAreaValue(input: HTMLTextAreaElement, value: string) {
|
|
const descriptor = Object.getOwnPropertyDescriptor(
|
|
HTMLTextAreaElement.prototype,
|
|
"value",
|
|
);
|
|
descriptor?.set?.call(input, value);
|
|
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
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}>
|
|
<DeveloperRequestPage />
|
|
</QueryClientProvider>,
|
|
);
|
|
});
|
|
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
return container;
|
|
}
|
|
|
|
describe("DeveloperRequestPage", () => {
|
|
it("opens the request modal and submits a request", async () => {
|
|
const container = await renderPage();
|
|
expect(container.textContent).toContain("신규 신청하기");
|
|
|
|
const actionButton = Array.from(container.querySelectorAll("button")).find(
|
|
(button) => button.textContent?.includes("신규 신청하기"),
|
|
);
|
|
expect(actionButton).toBeTruthy();
|
|
|
|
await act(async () => {
|
|
actionButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
});
|
|
|
|
expect(container.textContent).toContain("개발자 등록 신청");
|
|
|
|
const reasonField = container.querySelector(
|
|
"textarea",
|
|
) as HTMLTextAreaElement | null;
|
|
if (!reasonField) {
|
|
throw new Error("Expected reason textarea to be rendered");
|
|
}
|
|
|
|
await act(async () => {
|
|
await setTextAreaValue(reasonField, "Need RP access");
|
|
});
|
|
|
|
const submitButton = Array.from(container.querySelectorAll("button")).find(
|
|
(button) => button.textContent === "신청하기",
|
|
);
|
|
expect(submitButton).toBeTruthy();
|
|
|
|
await act(async () => {
|
|
submitButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
expect(requestDeveloperAccessMock).toHaveBeenCalled();
|
|
expect(requestDeveloperAccessMock.mock.calls[0]?.[0]).toEqual({
|
|
name: "Requester",
|
|
organization: "Hanmac",
|
|
reason: "Need RP access",
|
|
tenantId: "tenant-1",
|
|
});
|
|
});
|
|
|
|
it("shows a tenant-required notice and hides the request button when tenant is missing", async () => {
|
|
authState = {
|
|
user: {
|
|
access_token: "access-token",
|
|
profile: {
|
|
role: "user",
|
|
companyCode: "HANMAC",
|
|
name: "Requester",
|
|
email: "requester@example.com",
|
|
phone: "010-1234-5678",
|
|
},
|
|
},
|
|
};
|
|
fetchMeMock.mockResolvedValue({
|
|
id: "user-1",
|
|
name: "Requester",
|
|
email: "requester@example.com",
|
|
phone: "010-1234-5678",
|
|
role: "user",
|
|
});
|
|
|
|
const container = await renderPage();
|
|
expect(container.textContent).toContain(
|
|
"개발자 권한을 신청하려면 먼저 테넌트에 소속되어 있어야 합니다.",
|
|
);
|
|
expect(container.textContent).not.toContain("신규 신청하기");
|
|
expect(requestDeveloperAccessMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|