carbon-tokenization / backend /tests /persistence.test.ts
tfrere's picture
tfrere HF Staff
refactor(backend): modular server split with new routes, persistence and agent layer
f6678ab
/**
* Persistence Tests (Section 2 of TESTS.md)
*
* Tests debouncedSave, local file read/write, and flushAll.
* Uses a temporary directory to avoid polluting real data.
*/
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
import { mkdtempSync, rmSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
import * as Y from "yjs";
import { setDataDir, getDataDir, docPath } from "../src/utils.js";
import { debouncedSave, resetSaveTimers } from "../src/create-app.js";
let tmpDir: string;
beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), "collab-test-"));
mkdirSync(tmpDir, { recursive: true });
setDataDir(tmpDir);
});
afterEach(() => {
resetSaveTimers();
setDataDir(undefined);
try {
rmSync(tmpDir, { recursive: true, force: true });
} catch {}
});
function makeDoc(text: string): Y.Doc {
const ydoc = new Y.Doc();
const fragment = ydoc.getXmlFragment("default");
const el = new Y.XmlElement("paragraph");
el.insert(0, [new Y.XmlText(text)]);
fragment.insert(0, [el]);
return ydoc;
}
describe("2.1 Local persistence", () => {
it("2.1.1 debouncedSave writes .yjs file after debounce", async () => {
const ydoc = makeDoc("Hello world");
debouncedSave("test-doc", ydoc);
// File should NOT exist immediately (debounce is 2s)
const p = docPath("test-doc");
expect(existsSync(p)).toBe(false);
// Wait for debounce to fire
await new Promise((r) => setTimeout(r, 2500));
expect(existsSync(p)).toBe(true);
const buf = readFileSync(p);
expect(buf.length).toBeGreaterThan(0);
// Verify it's valid Yjs binary by applying it to a new doc
const restored = new Y.Doc();
Y.applyUpdate(restored, new Uint8Array(buf));
const fragment = restored.getXmlFragment("default");
expect(fragment.length).toBeGreaterThan(0);
});
it("2.1.2 docPath reads from configured DATA_DIR", () => {
// Write a .yjs file manually, then verify docPath points to it
const ydoc = makeDoc("Existing content");
const state = Y.encodeStateAsUpdate(ydoc);
const p = docPath("existing");
writeFileSync(p, Buffer.from(state));
// Read it back and verify content
expect(existsSync(p)).toBe(true);
const buf = readFileSync(p);
const restored = new Y.Doc();
Y.applyUpdate(restored, new Uint8Array(buf));
const fragment = restored.getXmlFragment("default");
expect(fragment.length).toBeGreaterThan(0);
});
it("2.1.3 debounce collapses rapid saves into one write", async () => {
const ydoc = makeDoc("Version 1");
// Trigger 3 rapid saves
debouncedSave("rapid", ydoc);
const ydoc2 = makeDoc("Version 2");
debouncedSave("rapid", ydoc2);
const ydoc3 = makeDoc("Version 3");
debouncedSave("rapid", ydoc3);
const p = docPath("rapid");
// Nothing written yet
expect(existsSync(p)).toBe(false);
// Wait for the single debounced write
await new Promise((r) => setTimeout(r, 2500));
expect(existsSync(p)).toBe(true);
// The file should contain the last version (ydoc3)
const buf = readFileSync(p);
const restored = new Y.Doc();
Y.applyUpdate(restored, new Uint8Array(buf));
const fragment = restored.getXmlFragment("default");
expect(fragment.length).toBeGreaterThan(0);
});
});