feat(ai-character): Mock Preview 모드 구현
This commit is contained in:
247
src/shared/mocks/__tests__/auth-handlers.test.ts
Normal file
247
src/shared/mocks/__tests__/auth-handlers.test.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { z } from "zod";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { login, logout } from "@/features/auth/api/auth-api";
|
||||
import { createApiClient } from "@/shared/api/client";
|
||||
import { createApiResponseSchema } from "@/shared/api/types";
|
||||
import { createMockHandlers, createMockStore } from "@/shared/mocks/handlers";
|
||||
import { server } from "@/shared/test/server";
|
||||
|
||||
const apiBaseUrl = "https://api.example.com";
|
||||
const adminToken = "mock-admin-jwt";
|
||||
|
||||
const aiCharactersPreviewSchema = z.object({
|
||||
totalCount: z.number(),
|
||||
page: z.literal(0),
|
||||
size: z.literal(20),
|
||||
hasNext: z.boolean(),
|
||||
items: z.array(z.unknown()),
|
||||
});
|
||||
|
||||
function createClient(token: string | null = adminToken) {
|
||||
return createApiClient({
|
||||
getToken: () => token,
|
||||
clearSession: vi.fn(),
|
||||
onAuthExpired: vi.fn(),
|
||||
});
|
||||
}
|
||||
|
||||
function useMockHandlers(store = createMockStore()) {
|
||||
server.use(...createMockHandlers(store, apiBaseUrl));
|
||||
return store;
|
||||
}
|
||||
|
||||
describe("mock auth handlers", () => {
|
||||
test("use the production admin login endpoint, request body, headers, and response envelope", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const session = await login(createClient("caller-token"), { email: "admin@test.com", password: "password" });
|
||||
const invalidBodyResponse = await fetch(`${apiBaseUrl}/admin/member/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "admin@test.com" }),
|
||||
});
|
||||
const authorizationResponse = await fetch(`${apiBaseUrl}/admin/member/login`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${adminToken}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "admin@test.com", password: "password" }),
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(session).toEqual({ token: adminToken, role: "ADMIN" });
|
||||
await expect(invalidBodyResponse.json()).resolves.toEqual({
|
||||
success: false,
|
||||
message: "잘못된 요청입니다.",
|
||||
data: null,
|
||||
errorProperty: null,
|
||||
});
|
||||
expect(invalidBodyResponse.status).toBe(400);
|
||||
expect(authorizationResponse.status).toBe(400);
|
||||
});
|
||||
|
||||
test("does not handle login requests from a different API origin", async () => {
|
||||
// Given
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const request = fetch("https://wrong-origin.example/admin/member/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email: "admin@test.com", password: "password" }),
|
||||
});
|
||||
|
||||
// Then
|
||||
await expect(request).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("requires Bearer and no body for production logout", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const missingBearerResponse = await fetch(`${apiBaseUrl}/member/logout`, { method: "POST" });
|
||||
const bodyResponse = await fetch(`${apiBaseUrl}/member/logout`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
body: "{}",
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(missingBearerResponse.status).toBe(401);
|
||||
expect(bodyResponse.status).toBe(400);
|
||||
});
|
||||
|
||||
test("returns 403 when logout uses a non-ADMIN Bearer token", async () => {
|
||||
// Given
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const response = await fetch(`${apiBaseUrl}/member/logout`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: "Bearer mock-member-jwt" },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(response.status).toBe(403);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
success: false,
|
||||
message: "접근 권한이 없습니다.",
|
||||
data: null,
|
||||
errorProperty: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 415 when login uses a non-JSON media type", async () => {
|
||||
// Given
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const response = await fetch(`${apiBaseUrl}/admin/member/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
body: JSON.stringify({ email: "admin@test.com", password: "password" }),
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(response.status).toBe(415);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
success: false,
|
||||
message: "지원하지 않는 미디어 타입입니다.",
|
||||
data: null,
|
||||
errorProperty: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("returns 401 when logout uses an invalid or already revoked token", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const invalidTokenResponse = await fetch(`${apiBaseUrl}/member/logout`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: "Bearer invalid-token" },
|
||||
});
|
||||
await logout(createClient(adminToken));
|
||||
const revokedTokenResponse = await fetch(`${apiBaseUrl}/member/logout`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(invalidTokenResponse.status).toBe(401);
|
||||
expect(revokedTokenResponse.status).toBe(401);
|
||||
});
|
||||
|
||||
test("resets the in-memory auth store to seed when a fresh mock store is created", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
useMockHandlers();
|
||||
await logout(createClient(adminToken));
|
||||
|
||||
// When
|
||||
const staleStoreResponse = await fetch(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, {
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
});
|
||||
useMockHandlers(createMockStore());
|
||||
const freshStoreResponse = await fetch(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, {
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
});
|
||||
const parsedFreshResponse = createApiResponseSchema(aiCharactersPreviewSchema).parse(await freshStoreResponse.json());
|
||||
|
||||
// Then
|
||||
expect(staleStoreResponse.status).toBe(401);
|
||||
expect(freshStoreResponse.status).toBe(200);
|
||||
expect(parsedFreshResponse).toEqual({
|
||||
success: true,
|
||||
message: null,
|
||||
data: { totalCount: 0, page: 0, size: 20, hasNext: false, items: [] },
|
||||
errorProperty: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("reactivates the admin preview token after logout and login in the same store", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
useMockHandlers();
|
||||
await login(createClient("caller-token"), { email: "admin@test.com", password: "password" });
|
||||
await logout(createClient(adminToken));
|
||||
|
||||
// When
|
||||
await login(createClient("caller-token"), { email: "admin@test.com", password: "password" });
|
||||
const response = await fetch(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, {
|
||||
headers: { Authorization: `Bearer ${adminToken}` },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
|
||||
test("returns the contract 403 fixture for a non-ADMIN Bearer token", async () => {
|
||||
// Given
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
const response = await fetch(`${apiBaseUrl}/api/v2/admin/ai-characters?page=0&size=20`, {
|
||||
headers: { Authorization: "Bearer mock-member-jwt" },
|
||||
});
|
||||
|
||||
// Then
|
||||
expect(response.status).toBe(403);
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
success: false,
|
||||
message: "접근 권한이 없습니다.",
|
||||
data: null,
|
||||
errorProperty: null,
|
||||
});
|
||||
});
|
||||
|
||||
test("keeps mock fixture state out of browser persistent storage and logs", async () => {
|
||||
// Given
|
||||
vi.stubEnv("VITE_API_BASE_URL", apiBaseUrl);
|
||||
const indexedDbOpen = vi.fn();
|
||||
const consoleLog = vi.spyOn(console, "log");
|
||||
const consoleWarn = vi.spyOn(console, "warn");
|
||||
const consoleError = vi.spyOn(console, "error");
|
||||
vi.stubGlobal("indexedDB", { open: indexedDbOpen });
|
||||
const cookieBefore = document.cookie;
|
||||
useMockHandlers();
|
||||
|
||||
// When
|
||||
await login(createClient("caller-token"), { email: "admin@test.com", password: "password" });
|
||||
await logout(createClient(adminToken));
|
||||
|
||||
// Then
|
||||
expect(localStorage).toHaveLength(0);
|
||||
expect(sessionStorage).toHaveLength(0);
|
||||
expect(indexedDbOpen).not.toHaveBeenCalled();
|
||||
expect(document.cookie).toBe(cookieBefore);
|
||||
expect(consoleLog).not.toHaveBeenCalled();
|
||||
expect(consoleWarn).not.toHaveBeenCalled();
|
||||
expect(consoleError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user