Files
voiceon-character-admin/src/shared/mocks/__tests__/auth-handlers.test.ts

245 lines
8.0 KiB
TypeScript

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();
});
});