const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const vm = require("node:vm"); const tick = () => new Promise((resolve) => setImmediate(resolve)); function runtime(cachedToken = "") { let app; const storage = new Map(cachedToken ? [["pet_token", cachedToken]] : []); const requests = [], logins = []; const wx = { getStorageSync: (key) => storage.get(key), setStorageSync: (key, value) => storage.set(key, value), removeStorageSync: (key) => storage.delete(key), login: (options) => logins.push(options), request: (options) => { requests.push(options); if (options.url.endsWith("/auth/login")) options.success({ statusCode: 200, data: { code: 0, data: { token: "fresh-token", user: { nickname: "毛孩家长9527" } }, }, }); else if (options.header.Authorization === "Bearer fresh-token") options.success({ statusCode: 200, data: { code: 0, data: "ok" } }); }, }; const module = { exports: {} }; vm.runInNewContext(fs.readFileSync("pets-fe/utils/api.js", "utf8"), { wx, getApp: () => app, require: () => ({ baseURL: "https://test.local/api" }), module, }); vm.runInNewContext(fs.readFileSync("pets-fe/app.js", "utf8"), { wx, App: (value) => { app = value; }, require: () => module.exports, }); return { app, api: module.exports, requests, logins, storage }; } test("启动即登录,多个首页请求共用登录任务并携带 token", async () => { const r = runtime(); r.app.onLaunch(); const pending = [ r.api.secure("/pets"), r.api.secure("/tasks"), r.api.secure("/me"), ]; assert.equal(r.logins.length, 1); assert.equal(r.requests.length, 0); r.logins[0].success({ code: "wechat-code" }); assert.deepEqual(await Promise.all(pending), ["ok", "ok", "ok"]); assert.equal( r.requests.filter((req) => req.url.endsWith("/auth/login")).length, 1, ); assert.ok( r.requests .slice(1) .every((req) => req.header.Authorization === "Bearer fresh-token"), ); assert.equal(r.storage.get("pet_token"), "fresh-token"); }); test("缓存 token 过期时只重新登录一次,延迟的旧 401 不清除新 token", async () => { const r = runtime("expired"); r.app.onLaunch(); const first = r.api.secure("/pets"), second = r.api.secure("/tasks"); await tick(); const old = r.requests.slice(); assert.equal(old.length, 2); old[0].success({ statusCode: 401, data: { message: "expired" } }); await tick(); assert.equal(r.logins.length, 1); r.logins[0].success({ code: "new-code" }); await first; old[1].success({ statusCode: 401, data: { message: "expired" } }); await second; assert.equal(r.app.globalData.token, "fresh-token"); assert.equal(r.logins.length, 1); }); function home(login, secure) { let definition; const wx = { getStorageSync: () => [], navigateTo: () => {} }; vm.runInNewContext(fs.readFileSync("pets-fe/pages/home/home.js", "utf8"), { wx, Date, getApp: () => ({ login }), Page: (value) => { definition = value; }, require: (name) => name.includes("environment") ? { cached: () => null } : name.includes("date") ? { localDate: () => "2026-10-05" } : { secure, toast: () => {} }, }); const page = { ...definition, data: { ...definition.data }, setData(value) { Object.assign(this.data, value); }, }; page.refreshEnvironment = () => new Promise(() => {}); return page; } function data(path) { return Promise.resolve( path === "/pets" || path === "/walks" ? [] : path === "/tasks" ? { items: [] } : { nickname: "毛孩家长9527" }, ); } test("登录和数据都准备好才显示首页,天气未完成也能进入", async () => { let finishLogin, calls = 0; const login = new Promise((resolve) => { finishLogin = resolve; }); const page = home( () => login, (path) => { calls++; return data(path); }, ); const loading = page.load(); page.load(); assert.equal(page.data.phase, "auth"); assert.equal(page.data.ready, false); assert.equal(calls, 0); finishLogin("token"); await loading; assert.equal(calls, 4); assert.equal(page.data.ready, true); assert.equal(page.data.loading, false); assert.equal(page.data.phase, "ready"); assert.equal(page.data.pets.length, 0); }); test("首次登录失败可重试,失败期间不请求业务数据", async () => { let attempts = 0, calls = 0; const page = home( async () => { if (++attempts === 1) throw new Error("无法连接"); return "token"; }, (path) => { calls++; return data(path); }, ); await page.load(); assert.equal(page.data.phase, "auth-error"); assert.equal(page.data.ready, false); assert.equal(calls, 0); await page.load(); assert.equal(page.data.ready, true); assert.equal(page.data.error, ""); }); test("首页请求失败不显示空数据状态,刷新失败保留已有记录", async () => { let fail = true; const page = home( async () => "token", (path) => (fail ? Promise.reject(new Error("网络异常")) : data(path)), ); await page.load(); assert.equal(page.data.phase, "data-error"); assert.equal(page.data.ready, false); fail = false; await page.load(); assert.equal(page.data.ready, true); page.setData({ pets: [{ id: "existing" }] }); fail = true; await page.load(); assert.equal(page.data.ready, true); assert.equal(page.data.pets[0].id, "existing"); assert.equal(page.data.error, "网络异常"); });