/** * 认证工具测试(token / user 持久化 + login/logout) */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getToken, setToken, clearToken, getUser, setUser, isAuthenticated, login, logout, } from "./auth"; import type { CurrentUser } from "@/types/view-models"; const mockUser: CurrentUser = { id: "u-test-001", email: "test@edu.test", name: "测试用户", roles: ["system_admin"], permissions: ["ADMIN_DASHBOARD_VIEW"], dataScope: "ALL", schoolId: null, schoolName: "Edu 实验学校", }; describe("auth token utilities", () => { beforeEach(() => { localStorage.clear(); vi.mocked(global.fetch).mockReset(); }); afterEach(() => { vi.restoreAllMocks(); }); it("setToken / getToken 持久化 token", () => { expect(getToken()).toBeNull(); setToken("mock-token-123"); expect(getToken()).toBe("mock-token-123"); }); it("clearToken 清除 token 和 user", () => { setToken("mock-token-123"); setUser(mockUser); clearToken(); expect(getToken()).toBeNull(); expect(getUser()).toBeNull(); }); it("setUser / getUser 持久化用户信息", () => { expect(getUser()).toBeNull(); setUser(mockUser); const user = getUser(); expect(user).not.toBeNull(); expect(user?.id).toBe("u-test-001"); expect(user?.email).toBe("test@edu.test"); }); it("getUser 在 JSON 损坏时返回 null", () => { localStorage.setItem("edu_user_info", "{invalid json}"); expect(getUser()).toBeNull(); }); it("isAuthenticated 基于 token 存在判断", () => { expect(isAuthenticated()).toBe(false); setToken("mock-token-123"); expect(isAuthenticated()).toBe(true); clearToken(); expect(isAuthenticated()).toBe(false); }); }); describe("login()", () => { beforeEach(() => { localStorage.clear(); vi.mocked(global.fetch).mockReset(); }); it("登录成功后持久化 token 和 user", async () => { vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ success: true, data: { user: mockUser, tokens: { accessToken: "jwt-token-success" }, }, }), } as unknown as Response); const result = await login("test@edu.test", "password"); expect(result.token).toBe("jwt-token-success"); expect(result.user.id).toBe("u-test-001"); expect(getToken()).toBe("jwt-token-success"); expect(getUser()?.id).toBe("u-test-001"); }); it("登录失败抛出错误", async () => { vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ success: false, error: { message: "邮箱或密码错误" }, }), } as unknown as Response); await expect(login("wrong@edu.test", "wrong-password")).rejects.toThrow( "邮箱或密码错误", ); expect(getToken()).toBeNull(); }); it("登录失败无 error.message 时使用默认错误信息", async () => { vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: async () => ({ success: false }), } as unknown as Response); await expect(login("a@b.c", "x")).rejects.toThrow("登录失败"); }); }); describe("logout()", () => { beforeEach(() => { localStorage.clear(); }); it("logout 清除 token 并跳转", () => { setToken("mock-token-123"); setUser(mockUser); expect(isAuthenticated()).toBe(true); // mock window.location.href 赋值(jsdom 不支持导航) const originalHref = window.location.href; const hrefSetter = vi.fn(); Object.defineProperty(window, "location", { writable: true, value: { ...window.location, set href(v: string) { hrefSetter(v); }, }, }); logout(); expect(isAuthenticated()).toBe(false); // 恢复 Object.defineProperty(window, "location", { writable: true, value: { href: originalHref }, }); }); });