| 1 | // Tests for the memory scratchpad's pure half (web/static/apps/memory.js — |
| 2 | // docs/eleven-memory.md). node test/memory.test.mjs |
| 3 | import assert from "node:assert/strict"; |
| 4 | import { sanitizeMemory, MAX_FACTS, MAX_FACT_CHARS } from "../web/static/apps/memory.js"; |
| 5 | |
| 6 | // sanitizeMemory clamps whatever the slot held — the sidecar and the panel |
| 7 | // share it unsigned, so read-side clamping is the invariant (mirror of the Go |
| 8 | // side's clampMemory; keep the two in step). |
| 9 | { |
| 10 | const raw = { |
| 11 | facts: [ |
| 12 | { id: "m1", text: " keeps replies short " }, |
| 13 | { text: "no id — repaired" }, |
| 14 | { id: "m3", text: " " }, // blank — dropped |
| 15 | { id: "bad id!", text: "hostile id — replaced" }, |
| 16 | { id: "m5", text: "x".repeat(MAX_FACT_CHARS + 50) }, |
| 17 | "not an object", |
| 18 | ], |
| 19 | }; |
| 20 | const m = sanitizeMemory(raw); |
| 21 | assert.equal(m.v, 1); |
| 22 | assert.equal(m.facts.length, 4); |
| 23 | assert.equal(m.facts[0].text, "keeps replies short"); |
| 24 | assert.ok(m.facts[1].id, "missing id repaired"); |
| 25 | assert.match(m.facts[2].id, /^[A-Za-z0-9_-]+$/, "hostile id replaced"); |
| 26 | assert.equal(m.facts[3].text.length, MAX_FACT_CHARS, "overlong fact clamped"); |
| 27 | } |
| 28 | |
| 29 | // The fact cap holds however many the blob carried. |
| 30 | { |
| 31 | const raw = { facts: Array.from({ length: MAX_FACTS + 10 }, (_, i) => ({ id: "m" + i, text: "fact " + i })) }; |
| 32 | assert.equal(sanitizeMemory(raw).facts.length, MAX_FACTS); |
| 33 | } |
| 34 | |
| 35 | // Hopeless shapes fold to empty, never throw. |
| 36 | for (const raw of [null, undefined, {}, { facts: "nope" }, { facts: 42 }]) { |
| 37 | const m = sanitizeMemory(raw); |
| 38 | assert.equal(m.v, 1); |
| 39 | assert.deepEqual(m.facts, []); |
| 40 | } |
| 41 | |
| 42 | console.log("memory.test.mjs: ALL PASSED"); |
| 43 | |