/**
* The offline mailbox (PROTOCOL.md §9): the bearer-token cache, the HTTP
* status → typed-result table, the deposit envelope BYTES (0x01 raw block /
* 0x02 kMbx-sealed identity record), the persistent queue with its dedup and
* caps, and the replay classifier's CID re-hash.
*
* Fixture: test/vectors/mailbox.json. The deposit envelopes are pinned under a
* deterministic getRandomValues stream (helpers/det-random.mjs), because the
* kMbx seal uses a random IV by design.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import { installDom, localStorageStub } from "./harness/dom.mjs";
import { mods, readVector, priv } from "./helpers/load.mjs";
installDom();
const M = await mods();
const { mailboxScript } = await import("./helpers/app-fakes.mjs");
test("the mailbox client, envelope bytes, queue and replay classifier", async () => {
const v = readVector("mailbox");
const got = await mailboxScript({ ...M, priv, localStorageStub });
assert.deepEqual(got.keys, v.keys, "the per-room localStorage keys are a storage contract");
assert.deepEqual(got.auth, v.auth);
assert.deepEqual(got.deposit, v.deposit);
assert.deepEqual(got.replay, v.replay);
assert.deepEqual(got.stateEmpty, v.stateEmpty);
assert.deepEqual(got.queueAfterEntry, v.queueAfterEntry, "the identity record is enqueued AHEAD of the entry");
// THE wire bytes: 0x02 | kMbx-seal(cid||block) then 0x01 | cid | block
assert.deepEqual(got.deposits, v.deposits);
assert.deepEqual(got.identEnvelopePrefix, v.identEnvelopePrefix);
assert.equal(got.identFlag, v.identFlag);
assert.deepEqual(got.sent, v.sent);
assert.deepEqual(got.queueAfterRepeat, v.queueAfterRepeat, "an already-deposited hash is never re-queued");
assert.deepEqual(got.qItemShapes, v.qItemShapes);
assert.deepEqual(got.overCapQueue, v.overCapQueue, "an over-frame-cap item is dropped like a 413");
assert.deepEqual(got.processed, v.processed);
// the stored queue-item shapes, key order spelled out (localStorage JSON)
assert.deepEqual(Object.keys(JSON.parse(v.qItemShapes.entry)), ["t", "hash"]);
assert.deepEqual(Object.keys(JSON.parse(v.qItemShapes.img)), ["t", "cid", "group"]);
});
test("a failed queue persist is SURFACED through onNotice, once — never swallowed", () => {
// The outgoing queue is uncapped (no silent drop-oldest — the suite's rule is
// bound WORK, never HISTORY), so the one remaining way to lose a queued item
// is localStorage refusing the write (quota, private mode). That failure must
// reach the user: save-queue! reads save-json's verdict and raises the room's
// onNotice exactly once per session.
localStorageStub.clear();
const notices = [];
const m = new M.MailboxSync({
log: {},
client: {},
cipher: {},
maxMessageKb: 64,
keys: {
cursor: M.nsKey("mbx-cursor:R"),
queue: M.nsKey("mbx-queue:R"),
sent: M.nsKey("mbx-sent:R"),
retry: M.nsKey("mbx-retry:R"),
ident: M.nsKey("mbx-ident:R"),
},
hasIdentity: false,
onNotice: (msg) => notices.push(msg),
});
const item = { t: "entry", hash: "h0" };
m._push(item);
// …and the queue itself is uncapped: well past the retired 500 drop-oldest
for (let i = 1; i <= 600; i++) m._push({ t: "entry", hash: `h${i}` });
assert.equal(m._queue.length, 601, "every queued op survives — nothing was dropped");
assert.equal(m._queue[0].hash, "h0", "the EARLIEST op survives — drop-oldest is gone");
const realSetItem = localStorageStub.setItem;
try {
localStorageStub.setItem = () => { throw new Error("QuotaExceededError"); };
m._dropHead(item); // synchronous persist path
assert.deepEqual(notices.length, 1, "the failed persist must be surfaced");
m._dropHead({ t: "entry", hash: "h1" });
assert.deepEqual(notices.length, 1, "…and only once per session");
} finally {
localStorageStub.setItem = realSetItem;
}
});