"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("node:fs"); const path = require("node:path"); const vm = require("node:vm"); const source = fs.readFileSync(path.join(__dirname, "tasks.js"), "utf8"); test("successful authorization sends numeric version and reloads", async () => { const requests = []; const harness = createHarness(async (_url, options) => { requests.push(options); return {ok: true, status: 200}; }); await harness.submit(); assert.equal(requests.length, 1); assert.equal(requests[0].headers["Content-Type"], "application/json"); assert.equal(requests[0].headers["X-CSRF-Token"], "csrf-token"); const payload = JSON.parse(requests[0].body); assert.equal(payload.start_key, "start-key"); assert.equal(typeof payload.tasks[0].expected_task_version, "number"); assert.equal(payload.tasks[0].expected_task_version, 7); assert.equal(harness.reloads(), 1); }); test("409 clears stale selection and requires a fresh choice", async () => { const harness = createHarness(async () => ({ok: false, status: 409})); await harness.submit(); assert.equal(harness.box.checked, false); assert.equal(harness.box.disabled, false); assert.equal(harness.button.disabled, true); assert.match(harness.feedback.textContent, /任务已变化/); }); for (const status of [400, 401, 403]) { test(`${status} releases the frozen payload for a page refresh`, async () => { const harness = createHarness(async () => ({ok: false, status})); await harness.submit(); assert.equal(harness.box.checked, true); assert.equal(harness.box.disabled, false); assert.equal(harness.button.disabled, false); assert.match(harness.feedback.textContent, /刷新页面后重试/); }); } test("5xx retries the byte-identical frozen payload", async () => { const bodies = []; const harness = createHarness(async (_url, options) => { bodies.push(options.body); return {ok: false, status: 503}; }); await harness.submit(); assert.equal(harness.box.disabled, true); assert.equal(harness.button.disabled, false); assert.match(harness.feedback.textContent, /原样重放/); await harness.submit(); assert.equal(bodies.length, 2); assert.equal(bodies[1], bodies[0]); }); test("network ambiguity retries the same payload and can finish", async () => { const bodies = []; let call = 0; const harness = createHarness(async (_url, options) => { bodies.push(options.body); call++; if (call === 1) throw new Error("network result unknown"); return {ok: true, status: 200}; }); await harness.submit(); assert.equal(harness.box.disabled, true); assert.match(harness.feedback.textContent, /原样重试/); await harness.submit(); assert.deepEqual(bodies, [bodies[0], bodies[0]]); assert.equal(harness.reloads(), 1); }); function createHarness(fetchImplementation) { class FakeElement { constructor() { this.dataset = {}; this.checked = false; this.disabled = false; this.indeterminate = false; this.textContent = ""; this.listeners = {}; } addEventListener(type, listener) { this.listeners[type] = listener; } } const box = new FakeElement(); box.checked = true; box.dataset = {taskId: "task-id", taskVersion: "7", price: "12.80"}; const selectAll = new FakeElement(); const summary = new FakeElement(); const button = new FakeElement(); const feedback = new FakeElement(); const form = new FakeElement(); form.dataset = {startKey: "start-key", csrf: "csrf-token"}; form.querySelector = (selector) => ({ "[data-select-all]": selectAll, "[data-selection-summary]": summary, "[data-start-button]": button, "[data-start-feedback]": feedback, })[selector] || null; form.querySelectorAll = (selector) => selector === "input[data-task-id]" ? [box] : []; let reloadCount = 0; const context = { document: {querySelector: (selector) => selector === "[data-start-purchases]" ? form : null}, fetch: fetchImplementation, window: {location: {reload: () => { reloadCount++; }}}, }; vm.runInNewContext(source, context, {filename: "tasks.js"}); return { box, button, feedback, reloads: () => reloadCount, submit: () => form.listeners.submit({preventDefault() {}}), }; }