Files
deerflow-code/frontend-web/scripts/test-users-paged.mjs
T

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" });
});