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(), content: 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: 2, content: expect.arrayContaining([expect.objectContaining({ name: "루나" })]) }, 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(); }); });