// 会话回归测试:加载实际请求层和 store,以可控 HTTP 响应验证过期恢复,避免时间等待造成不稳定。 import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import ts from "typescript"; import { createPinia, setActivePinia } from "pinia"; const storage = new Map(); globalThis.sessionStorage = { getItem: (key) => storage.get(key) ?? null, setItem: (key, value) => storage.set(key, value), removeItem: (key) => storage.delete(key), }; let expired = 0; globalThis.window = { dispatchEvent: (event) => { if (event.type === "session-expired") expired++; }, }; // Node 不提供 Vite 的 import.meta.env;只替换构建时基地址,不改动被测函数。 const asModule = (source) => "data:text/javascript;base64," + Buffer.from( ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ES2022 }, }).outputText, ).toString("base64"); const apiSource = await readFile(new URL("../src/api.ts", import.meta.url), "utf8"); const apiUrl = asModule(apiSource.replace("import.meta.env.VITE_API_BASE", '"/api/v1"')); const api = await import(apiUrl); const storeSource = await readFile(new URL("../src/store.ts", import.meta.url), "utf8"); const { useSession } = await import( asModule( storeSource .replace('from "./api"', `from ${JSON.stringify(apiUrl)}`) .replace('from "pinia"', `from ${JSON.stringify(import.meta.resolve("pinia"))}`), ) ); setActivePinia(createPinia()); const session = useSession(); const oldTokens = { access_token: "expired-test-access", refresh_token: "old-test-refresh" }; const newTokens = { access_token: "renewed-test-access", refresh_token: "rotated-test-refresh" }; const result = (status, data) => Response.json(status < 400 ? { data, message: "ok", request_id: "test" } : { error: { code: "UNAUTHORIZED", message: "测试过期凭据" }, request_id: "test", }, { status }); api.saveTokens(oldTokens); let refreshes = 0; globalThis.fetch = async (url, options = {}) => { if (url.endsWith("/auth/refresh")) { refreshes++; assert.equal(JSON.parse(options.body).refresh_token, oldTokens.refresh_token); return result(200, newTokens); } return options.headers.get("Authorization") === `Bearer ${newTokens.access_token}` ? result(200, { id: 1, role: "employee" }) : result(401); }; await session.load(); assert.equal(session.user.id, 1); assert.equal(refreshes, 1); assert.equal(api.tokens().refresh_token, newTokens.refresh_token); console.log("PASS /auth/me 过期时自动刷新并恢复用户"); api.saveTokens(oldTokens); refreshes = 0; await Promise.all([api.api("/audit"), api.api("/dashboard/stats")]); assert.equal(refreshes, 1); console.log("PASS 并发请求只轮换一次刷新令牌"); api.saveTokens(oldTokens); refreshes = 0; await assert.rejects(api.api("/auth/login", "POST", { username: "test", password: "test" }), (error) => error.status === 401); assert.equal(refreshes, 0); console.log("PASS 登录失败不触发递归刷新"); api.saveTokens(oldTokens); let revoked = false; globalThis.fetch = async (url, options = {}) => { if (url.endsWith("/auth/refresh")) return result(200, newTokens); if (url.endsWith("/auth/logout")) { assert.equal(JSON.parse(options.body).refresh_token, newTokens.refresh_token); assert.equal(options.headers.get("Authorization"), `Bearer ${newTokens.access_token}`); revoked = true; return result(200, { revoked: true }); } return options.headers.get("Authorization") === `Bearer ${newTokens.access_token}` ? result(200, { id: 1, role: "employee" }) : result(401); }; await session.logout(); assert.equal(revoked, true); assert.equal(api.tokens().access_token, undefined); assert.equal(session.user, null); console.log("PASS 退出撤销轮换后的令牌并清理本地会话"); api.saveTokens(oldTokens); expired = 0; let calls = 0; globalThis.fetch = async () => { calls++; return result(401); }; await assert.rejects(api.api("/auth/me"), (error) => error.status === 401); assert.equal(calls, 2); assert.equal(expired, 1); assert.equal(api.tokens().access_token, undefined); console.log("PASS 刷新失败终止重试并通知登录页面");