feat(admin-portal): 完成参考项目差距闭环 - 4 批次补齐 + Vitest 测试
差距分析闭环(workline §7-§8): - P0:审计子模块 3 页 + 学校组织 7 页 - P1:系统设置 4 Card + 公告管理 + 邀请码管理 - P2:文件管理 + AI 配置 - P6:5 测试文件 / 69 用例全过 共享基础设施: - permissions +11 权限点 +14 路由 - view-models +20 接口 - i18n +100 key - graphql-client +25 operation - fixtures/handlers +15 mock +20 handler 质量:typecheck + lint 零错误,vitest 69/69 通过,arch.db 已更新
This commit is contained in:
157
apps/admin-portal/src/lib/auth.test.ts
Normal file
157
apps/admin-portal/src/lib/auth.test.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 认证工具测试(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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user