91 lines
3.8 KiB
JavaScript
91 lines
3.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { createRequire } from "node:module";
|
|
import path from "node:path";
|
|
import { test } from "node:test";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const root = fileURLToPath(new URL("../", import.meta.url));
|
|
const require = createRequire(import.meta.url);
|
|
const viteRequire = createRequire(require.resolve("vite/package.json"));
|
|
const { build, stop } = viteRequire("esbuild");
|
|
const outfile = path.join(root, ".runtime/unit-tests/users-paged-api.mjs");
|
|
try {
|
|
await build({
|
|
absWorkingDir: root,
|
|
entryPoints: ["src/core/auth/api.ts"],
|
|
outfile,
|
|
bundle: true,
|
|
platform: "node",
|
|
format: "esm",
|
|
plugins: [{
|
|
name: "mock-user-api-dependencies",
|
|
setup(builder) {
|
|
builder.onResolve({ filter: /^@\/core\/(api\/fetch-client|config)$/ }, ({ path }) => ({ path, namespace: "mock" }));
|
|
builder.onLoad({ filter: /.*/, namespace: "mock" }, ({ path }) => ({
|
|
contents: path.endsWith("config")
|
|
? 'export const getBackendBaseURL = () => "https://example.test";'
|
|
: 'export const apiFetch = (...args) => globalThis.usersPagedFetch(...args);',
|
|
}));
|
|
},
|
|
}],
|
|
});
|
|
} finally {
|
|
stop();
|
|
}
|
|
const { listUsersPaged } = await import(pathToFileURL(outfile).href);
|
|
const fetchPage = (signal) => listUsersPaged({ q: "alice", limit: 20, offset: 40 }, signal);
|
|
|
|
test("whitelist requests pagination and search without statistics", async () => {
|
|
const users = { items: [{ id: "u1", email: "one@example.test", approved: false }], total: 41, approved_count: 40, limit: 20, offset: 40 };
|
|
globalThis.usersPagedFetch = async (url, init) => {
|
|
assert.equal(url, "https://example.test/api/v1/auth/users?limit=20&offset=40&include_counts=false&q=alice");
|
|
assert.ok(init.signal instanceof AbortSignal);
|
|
return Response.json(users);
|
|
};
|
|
assert.deepEqual(await fetchPage(), users);
|
|
});
|
|
|
|
test("server timeout detail reaches the retry UI", async () => {
|
|
globalThis.usersPagedFetch = async () => Response.json({ detail: "用户基础信息查询超时,请稍后重试" }, { status: 503 });
|
|
await assert.rejects(fetchPage(), /用户基础信息查询超时/);
|
|
});
|
|
|
|
test("client deadline aborts pending request and clears its timer", async (t) => {
|
|
let deadline;
|
|
const timer = {};
|
|
t.mock.method(globalThis, "setTimeout", (callback, delay) => {
|
|
assert.equal(delay, 20_000);
|
|
deadline = callback;
|
|
return timer;
|
|
});
|
|
const cleared = t.mock.method(globalThis, "clearTimeout", (value) => assert.equal(value, timer));
|
|
globalThis.usersPagedFetch = (_url, { signal }) => new Promise((_resolve, reject) => {
|
|
signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
|
});
|
|
const pending = fetchPage();
|
|
deadline();
|
|
await assert.rejects(pending, /用户列表加载超时/);
|
|
assert.equal(cleared.mock.callCount(), 1);
|
|
});
|
|
|
|
test("query cancellation propagates without being reported as a timeout", async () => {
|
|
const query = new AbortController();
|
|
globalThis.usersPagedFetch = (_url, { signal }) => new Promise((_resolve, reject) => {
|
|
signal.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
|
});
|
|
const pending = fetchPage(query.signal);
|
|
query.abort();
|
|
await assert.rejects(pending, { name: "AbortError" });
|
|
});
|
|
|
|
test("non-JSON errors preserve HTTP status", async () => {
|
|
globalThis.usersPagedFetch = async () => new Response("unavailable", { status: 502 });
|
|
await assert.rejects(fetchPage(), /HTTP 502/);
|
|
});
|
|
test("already cancelled requests remain cancellation errors", async () => {
|
|
const controller = new AbortController();
|
|
controller.abort();
|
|
globalThis.usersPagedFetch = async (_url, { signal }) => { signal.throwIfAborted(); };
|
|
await assert.rejects(fetchPage(controller.signal), { name: "AbortError" });
|
|
});
|