76 lines
3.8 KiB
TypeScript
76 lines
3.8 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { QuestionnaireEngine } from "../src/lib/questionnaire/questionnaire-engine";
|
|
import { SaveEventRecorder, type SaveEvent } from "../src/lib/persistence/save-events";
|
|
import type { SaveRequest } from "../src/lib/persistence/types";
|
|
import example from "../src/demo/example-questionnaire.json";
|
|
|
|
test("file saves reject missing upload IDs and preserve real host references", async () => {
|
|
const fileEngine = new QuestionnaireEngine({
|
|
schemaVersion: 1, id: "uploads", version: "1", title: "Uploads",
|
|
pages: [{ id: "files", title: "Files", fields: [{ type: "files", label: "Attachment", id: "attachment" }] }]
|
|
});
|
|
const calls: SaveRequest[] = [];
|
|
const recorder = new SaveEventRecorder(fileEngine, () => {}, {}, 0, undefined, request => {
|
|
calls.push(request); return { revision: 1 };
|
|
});
|
|
const metadata = { name: "evidence.pdf", size: 10, type: "application/pdf" };
|
|
assert.equal(await recorder.filesSelected("attachment", [metadata], "files", fileEngine.createSession({ attachment: [metadata] })), false);
|
|
assert.equal(calls.length, 0);
|
|
const uploaded = { ...metadata, uploadId: "host-upload-123" };
|
|
const session = fileEngine.createSession({ attachment: [uploaded] });
|
|
assert.equal(await recorder.filesSelected("attachment", [uploaded], "files", session), true);
|
|
assert.deepEqual(calls[0].changes.attachment, [uploaded]);
|
|
assert.deepEqual(session.getEffectiveAnswers().attachment, [uploaded]);
|
|
});
|
|
|
|
test("failed changes retry and queued saves use the acknowledged revision", async () => {
|
|
const engine = new QuestionnaireEngine(example);
|
|
const calls: SaveRequest[] = [];
|
|
const events: SaveEvent[] = [];
|
|
let fail = true;
|
|
const recorder = new SaveEventRecorder(engine, event => events.push(event), {}, 4, undefined, async request => {
|
|
calls.push(request);
|
|
if (fail) { fail = false; throw new Error("Offline"); }
|
|
return { revision: request.revision + 1 };
|
|
});
|
|
const session = engine.createSession({ fullName: "Alex" });
|
|
assert.equal(await recorder.save(session, ["fullName"], "blur", "registration"), false);
|
|
const retry = recorder.save(session, ["fullName"], "Continue", "registration");
|
|
const next = recorder.savePosition("registration", "attending");
|
|
assert.equal(await retry, true);
|
|
await next;
|
|
assert.deepEqual(calls[0].changes, calls[1].changes);
|
|
assert.deepEqual(calls.map(call => call.revision), [4, 4, 5]);
|
|
assert.deepEqual(events.map(event => event.status), ["Blocked", "Saved", "Saved"]);
|
|
});
|
|
|
|
test("save promises wait for the host and duplicate queued answers are deduplicated", async () => {
|
|
const engine = new QuestionnaireEngine(example);
|
|
let finish!: () => void;
|
|
const gate = new Promise<void>(resolve => { finish = resolve; });
|
|
let calls = 0;
|
|
const events: SaveEvent[] = [];
|
|
const recorder = new SaveEventRecorder(engine, event => events.push(event), {}, 0, undefined, async () => {
|
|
calls++; await gate; return { revision: 1 };
|
|
});
|
|
const session = engine.createSession({ fullName: "Alex" });
|
|
const first = recorder.save(session, ["fullName"], "blur", "registration");
|
|
const second = recorder.save(session, ["fullName"], "blur", "registration");
|
|
await Promise.resolve();
|
|
assert.equal(events.length, 0);
|
|
finish();
|
|
assert.equal(await first, true);
|
|
assert.equal(await second, true);
|
|
assert.equal(calls, 1);
|
|
});
|
|
|
|
test("in-memory changes never claim server persistence or advance revisions", async () => {
|
|
const engine = new QuestionnaireEngine(example);
|
|
const events: SaveEvent[] = [];
|
|
const recorder = new SaveEventRecorder(engine, event => events.push(event));
|
|
await recorder.save(engine.createSession({ fullName: "Alex" }), ["fullName"], "blur", "registration");
|
|
await recorder.savePosition("registration", "attending");
|
|
assert.ok(events.every(event => event.status === "Changed" && event.data?.revision === 0));
|
|
});
|