151 lines
5.1 KiB
TypeScript
151 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
|
|
import { render, screen, fireEvent, cleanup, waitFor } from "@testing-library/react";
|
|
|
|
import LoginPage from "@/app/login/page";
|
|
|
|
// next/navigation 의 useRouter 를 목으로 대체해 리다이렉트 동작을 검증한다.
|
|
export const pushMock = vi.fn();
|
|
|
|
vi.mock("next/navigation", () => {
|
|
return {
|
|
__esModule: true,
|
|
useRouter: () => ({ push: pushMock }),
|
|
};
|
|
});
|
|
|
|
describe("LoginPage", () => {
|
|
beforeEach(() => {
|
|
vi.stubGlobal("fetch", vi.fn());
|
|
});
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.unstubAllGlobals();
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("올바른 이메일/비밀번호로 로그인하면 /api/auth/login 으로 요청을 보내고 /dashboard 로 이동해야 한다", async () => {
|
|
const fetchMock = vi.fn().mockImplementation((input: any, init?: any) => {
|
|
const url = typeof input === "string" ? input : input.url;
|
|
const method = init?.method ?? "GET";
|
|
|
|
if (url === "/api/auth/me" && method === "GET") {
|
|
return Promise.resolve(
|
|
new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
|
|
status: 401,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (url === "/api/auth/login" && method === "POST") {
|
|
return Promise.resolve(
|
|
new Response(JSON.stringify({ id: "1", email: "user@example.com" }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
}
|
|
|
|
return Promise.resolve(new Response(null, { status: 500 }));
|
|
});
|
|
|
|
vi.stubGlobal("fetch", fetchMock as any);
|
|
|
|
render(<LoginPage />);
|
|
|
|
const emailInput = screen.getByLabelText("이메일") as HTMLInputElement;
|
|
const passwordInput = screen.getByLabelText("비밀번호") as HTMLInputElement;
|
|
const submitButton = screen.getByRole("button", { name: "로그인" });
|
|
|
|
fireEvent.change(emailInput, { target: { value: "user@example.com" } });
|
|
fireEvent.change(passwordInput, { target: { value: "securePass1" } });
|
|
|
|
fireEvent.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
const loginCall = fetchMock.mock.calls.find(([url, options]) => {
|
|
return url === "/api/auth/login" && options?.method === "POST";
|
|
});
|
|
|
|
expect(loginCall).toBeDefined();
|
|
});
|
|
|
|
const loginCall = fetchMock.mock.calls.find(([url]) => url === "/api/auth/login") as any;
|
|
const [url, options] = loginCall;
|
|
expect(url).toBe("/api/auth/login");
|
|
expect(options.method).toBe("POST");
|
|
expect(options.headers["Content-Type"]).toBe("application/json");
|
|
|
|
const body = JSON.parse(options.body);
|
|
expect(body.email).toBe("user@example.com");
|
|
expect(body.password).toBe("securePass1");
|
|
|
|
await waitFor(() => {
|
|
expect(pushMock).toHaveBeenCalledWith("/dashboard");
|
|
});
|
|
});
|
|
|
|
it("로그인 실패 시 에러 메시지를 화면에 표시해야 한다", async () => {
|
|
const fetchMock = vi.fn().mockImplementation((input: any, init?: any) => {
|
|
const url = typeof input === "string" ? input : input.url;
|
|
const method = init?.method ?? "GET";
|
|
|
|
if (url === "/api/auth/me" && method === "GET") {
|
|
return Promise.resolve(
|
|
new Response(JSON.stringify({ message: "인증이 필요합니다." }), {
|
|
status: 401,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (url === "/api/auth/login" && method === "POST") {
|
|
return Promise.resolve(
|
|
new Response(JSON.stringify({ message: "이메일 또는 비밀번호가 올바르지 않습니다." }), {
|
|
status: 401,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
}
|
|
|
|
return Promise.resolve(new Response(null, { status: 500 }));
|
|
});
|
|
|
|
vi.stubGlobal("fetch", fetchMock as any);
|
|
|
|
render(<LoginPage />);
|
|
|
|
const emailInput = screen.getByLabelText("이메일") as HTMLInputElement;
|
|
const passwordInput = screen.getByLabelText("비밀번호") as HTMLInputElement;
|
|
const submitButton = screen.getByRole("button", { name: "로그인" });
|
|
|
|
fireEvent.change(emailInput, { target: { value: "user@example.com" } });
|
|
fireEvent.change(passwordInput, { target: { value: "wrongpass" } });
|
|
|
|
fireEvent.click(submitButton);
|
|
|
|
const errorText = await screen.findByText(/이메일 또는 비밀번호가 올바르지 않습니다./);
|
|
expect(errorText).toBeTruthy();
|
|
});
|
|
|
|
it("이미 로그인된 상태에서 /login 에 접근하면 /dashboard 로 리다이렉트해야 한다", async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue(
|
|
new Response(JSON.stringify({ id: "1", email: "user@example.com", tokenVersion: 1 }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
vi.stubGlobal("fetch", fetchMock as any);
|
|
|
|
render(<LoginPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
expect(fetchMock.mock.calls[0][0]).toBe("/api/auth/me");
|
|
expect(pushMock).toHaveBeenCalledWith("/dashboard");
|
|
});
|
|
});
|
|
});
|