194 lines
5.5 KiB
JavaScript
194 lines
5.5 KiB
JavaScript
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("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, "网络异常");
|
|
});
|