Files
Sybil-2/web/tests/agent-push.test.mjs
T

468 lines
30 KiB
JavaScript
Raw Normal View History

2026-10-14 22:03:21 -07:00
import assert from "node:assert/strict";
import test from "node:test";
import { randomUUID } from "node:crypto";
import {
AgentPushClient, agentPushAuthorityScope, agentPushCursor, createAgentPresenceReporter, createAgentPushController,
MAX_AGENT_PUSH_REVOCATIONS, parseAgentPushConfig, parseAgentPushConsent,
} from "../src/lib/agent-push.ts";
import { agentAuthFrame, applyAgentEvent, createAgentRoomState, parseAgentRoomEvent } from "../src/lib/agent-room.ts";
const sourceId = "4599d348-e053-4937-987d-1392746fb2ce";
const replacementSourceId = "22222222-2222-4222-8222-222222222222";
const configuration = () => ({ version: 1, enabled: true, webPushAvailable: true, apnsAvailable: false, sourceId, quarantined: false,
vapidPublicKey: "B".repeat(87), policy: { presenceHeartbeatMs: 15000, presenceLeaseMs: 60000 } });
const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); return { promise, resolve, reject }; };
const settle = () => new Promise(resolve => setImmediate(resolve));
function fixture(overrides = {}, shared = {}) {
let record = shared.record || null, subscription = null, permission = "default", support = "supported", config = configuration();
const calls = [], states = [];
const api = {
config: async () => config,
register: async (id, value) => { calls.push(["register", id, value]); },
revoke: async id => { calls.push(["revoke", id]); },
...overrides.api,
};
const makeSubscription = (endpoint = "https://push.example.test/opaque") => ({
toJSON: () => ({ endpoint, keys: { p256dh: "fixture-public", auth: "fixture-auth" } }),
unsubscribe: async () => { calls.push(["unsubscribe"]); subscription = null; return true; },
});
const platform = {
support: () => support, permission: () => permission,
requestPermission: () => { calls.push(["permission"]); permission = "granted"; return Promise.resolve(permission); },
subscription: async () => subscription,
subscribe: async key => { calls.push(["subscribe", key]); return subscription = makeSubscription(); },
clearNotifications: async () => { calls.push(["clear"]); },
...overrides.platform,
};
const options = {
api, platform, read: () => record, write: value => { record = structuredClone(value); }, uuid: randomUUID,
scope: async () => "a".repeat(64),
lock: async operation => operation(), changed: state => states.push(state), ...shared.options,
};
const controller = createAgentPushController(options);
return { controller, options, calls, states, api, platform, makeSubscription, record: () => record,
setConfig(value) { config = value; }, setPermission(value) { permission = value; }, setSupport(value) { support = value; }, setSubscription(value) { subscription = value; } };
}
test("explicit gesture requests permission before yielding; permission alone never opts in", async () => {
const f = fixture(); f.setPermission("granted");
await f.controller.refresh();
assert.equal(f.record(), null); assert.deepEqual(f.calls, []);
const enabling = f.controller.enable();
assert.deepEqual(f.calls, [["permission"]], "request runs directly inside the click stack");
await enabling;
assert.equal(f.record().enabled, true);
assert.equal(f.record().sourceId, sourceId);
assert.equal(f.record().revocationPending, false);
assert.equal(f.calls.filter(([name]) => name === "register").length, 1);
await f.controller.refresh(); await f.controller.refresh();
assert.equal(f.calls.filter(([name]) => name === "register").length, 1, "heartbeat does not repeatedly register an unchanged subscription");
});
test("denial, disabled config, unsupported browser and iOS installation guidance do not subscribe", async () => {
for (const support of ["unsupported", "install"]) {
const f = fixture(); f.setSupport(support); await f.controller.refresh(); await f.controller.enable();
assert.equal(f.controller.state().canEnable, false); assert.deepEqual(f.calls, []);
assert.match(f.controller.state().message, support === "install" ? /Home Screen/ : /HTTPS/);
}
const disabled = fixture(); disabled.setConfig({ ...configuration(), enabled: false });
await disabled.controller.refresh(); await disabled.controller.enable(); assert.deepEqual(disabled.calls, []);
const denied = fixture({ platform: { requestPermission: async () => "denied" } });
await denied.controller.refresh(); await denied.controller.enable();
assert.equal(denied.record().enabled, false); assert.equal(denied.calls.length, 0);
assert.match(denied.controller.state().error, /not allowed/);
});
test("registration failure rolls back subscription, persists revoke intent and never echoes provider errors", async () => {
const f = fixture({ api: { register: async () => { throw new Error("https://private-provider/token-secret"); }, revoke: async () => { throw new Error("offline"); } } });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.record().enabled, false); assert.equal(f.record().revocationPending, true);
assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
assert.doesNotMatch(JSON.stringify(f.states), /private-provider|token-secret/);
assert.equal(f.controller.state().canEnable, false);
});
test("offline logout writes tombstone synchronously, unsubscribes locally and retries before any enable", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const id = f.record().installationId;
f.api.revoke = async () => { throw new Error("offline"); };
const disabling = f.controller.disable();
assert.equal(f.record().enabled, false); assert.equal(f.record().revocationPending, true);
await disabling;
assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
const count = f.calls.filter(([name]) => name === "register").length;
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.calls.filter(([name]) => name === "register").length, count);
f.api.revoke = async revoked => { assert.equal(revoked, id); };
await f.controller.refresh();
assert.equal(f.record().revocationPending, false); assert.equal(f.record().enabled, false);
});
test("storage failures during logout still revoke, unsubscribe and close notifications", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
f.options.write = () => { throw new Error("quota"); };
let removed = false; f.options.remove = () => { removed = true; };
await f.controller.disable();
assert.ok(removed); assert.equal(f.controller.state().enabled, false);
for (const name of ["revoke", "unsubscribe", "clear"]) assert.ok(f.calls.some(([action]) => action === name));
await f.controller.refresh();
assert.equal(f.controller.state().enabled, false);
});
test("a stuck cross-tab lock cannot prevent server retirement or local logout cleanup", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
f.options.lock = async () => { throw new Error("lock acquisition expired"); };
await f.controller.disable();
assert.equal(f.record().enabled, false); assert.equal(f.record().revocationPending, false);
for (const name of ["revoke", "unsubscribe", "clear"]) assert.ok(f.calls.some(([action]) => action === name));
});
test("synchronous permission API failures remain a recoverable opt-in error", async () => {
const f = fixture({ platform: { requestPermission: () => { throw new Error("user gesture rejected"); } } });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.record().enabled, false); assert.match(f.controller.state().error, /could not be requested/);
});
test("subscription rotation upserts the same installation; permission revocation removes it", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const id = f.record().installationId;
f.setSubscription(f.makeSubscription("https://push.example.test/rotated"));
await f.controller.refresh();
const registrations = f.calls.filter(([name]) => name === "register");
assert.equal(registrations.length, 2); assert.ok(registrations.every(([, installation]) => installation === id));
f.setPermission("denied"); await f.controller.refresh();
assert.equal(f.record().enabled, false); assert.equal(f.record().revocationPending, false);
});
test("logout while permission prompt is open cancels the older opt-in", async () => {
const prompt = deferred(), f = fixture({ platform: { requestPermission: () => prompt.promise } });
await f.controller.refresh(); const enabling = f.controller.enable();
await f.controller.disable(); prompt.resolve("granted"); await enabling;
assert.equal(f.record().enabled, false);
assert.ok(!f.calls.some(([name]) => name === "subscribe" || name === "register"));
});
test("two-tab enable → disable → enable uses permanent server tombstones and a new installation UUID", async () => {
let sharedRecord = null, queue = Promise.resolve(), currentRegistration = false;
const calls = [], pendingRegister = deferred(), retired = new Set(); let first = true;
const common = { options: {
read: () => sharedRecord, write: value => { sharedRecord = structuredClone(value); },
lock: operation => { const running = queue.then(operation); queue = running.catch(() => undefined); return running; },
} };
const api = {
register: async id => { calls.push("register"); if (first) { first = false; await pendingRegister.promise; } assert.ok(!retired.has(id), "retired UUID must never be reused"); currentRegistration = true; },
revoke: async id => { calls.push("revoke"); retired.add(id); currentRegistration = false; },
};
const a = fixture({ api }, common), b = fixture({ api }, common);
await a.controller.refresh(); await b.controller.refresh();
const enabling = a.controller.enable(); await settle();
assert.deepEqual(calls, ["register"]);
const disabling = b.controller.disable();
assert.equal(sharedRecord.enabled, false); assert.equal(sharedRecord.revocationPending, true);
pendingRegister.resolve(); await Promise.all([enabling, disabling]);
assert.equal(currentRegistration, false);
const id = sharedRecord.installationId;
await b.controller.enable();
assert.equal(currentRegistration, true); assert.equal(sharedRecord.enabled, true); assert.notEqual(sharedRecord.installationId, id);
assert.ok(retired.has(id));
assert.equal(calls.at(-1), "register");
});
test("a timed-out old PUT completing after DELETE cannot revive the retired installation or affect a new opt-in", async () => {
const retired = new Set(), active = new Set(), late = deferred(); let firstId, lateStatus, first = true;
const f = fixture({ api: {
register: async id => {
if (first) {
first = false; firstId = id;
late.promise.then(() => { lateStatus = retired.has(id) ? 409 : 200; if (lateStatus === 200) active.add(id); });
throw new Error("request timed out while server is still processing");
}
assert.ok(!retired.has(id)); active.add(id);
},
revoke: async id => { retired.add(id); active.delete(id); },
} });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.record().enabled, false); assert.equal(f.record().revocationPending, false); assert.ok(retired.has(firstId));
await f.controller.enable(); const currentId = f.record().installationId;
assert.notEqual(currentId, firstId); assert.ok(active.has(currentId));
late.resolve(); await settle();
assert.equal(lateStatus, 409); assert.deepEqual([...active], [currentId]);
});
test("authority changes retain original-server cleanup without issuing a misleading DELETE to the new server", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const oldId = f.record().installationId, registrations = f.calls.filter(([name]) => name === "register").length;
f.options.scope = async () => "b".repeat(64);
await f.controller.refresh(); await f.controller.refresh();
assert.equal(f.record().enabled, false);
assert.equal(f.record().revocationPending, false, "local unsubscribe succeeded, but old server cleanup stays queued");
assert.deepEqual(f.record().pendingRevocations, [{ installationId: oldId, scope: "a".repeat(64) }]);
assert.ok(!f.calls.some(([name]) => name === "revoke"), "new server's unknown-ID DELETE/204 cannot retire the original server registration");
assert.equal(f.calls.filter(([name]) => name === "register").length, registrations);
await f.controller.enable();
const newId = f.record().installationId;
assert.notEqual(newId, oldId); assert.equal(f.record().scope, "b".repeat(64)); assert.equal(f.record().enabled, true);
assert.deepEqual(f.record().pendingRevocations, [{ installationId: oldId, scope: "a".repeat(64) }]);
const reloaded = createAgentPushController(f.options);
await reloaded.refresh();
assert.deepEqual(f.record().pendingRevocations, [{ installationId: oldId, scope: "a".repeat(64) }], "queue survives a reload on B");
assert.ok(!f.calls.some(([name]) => name === "revoke"));
f.options.scope = async () => "a".repeat(64);
await reloaded.refresh();
assert.deepEqual(f.calls.filter(([name]) => name === "revoke"), [["revoke", oldId]], "returning with A credentials retries only A's intent");
assert.deepEqual(f.record().pendingRevocations, [{ installationId: newId, scope: "b".repeat(64) }]);
assert.equal(f.record().enabled, false);
await reloaded.enable();
assert.equal(f.record().enabled, true); assert.equal(f.record().scope, "a".repeat(64));
assert.notEqual(f.record().installationId, oldId);
});
test("revoked old credentials do not prevent fresh opt-in after same-server credential rotation", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const oldId = f.record().installationId;
f.api.revoke = async () => { throw new Error("old credentials revoked"); };
await f.controller.disable();
assert.equal(f.record().revocationPending, true);
f.options.scope = async () => "c".repeat(64);
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.record().enabled, true); assert.equal(f.record().scope, "c".repeat(64));
assert.notEqual(f.record().installationId, oldId);
assert.deepEqual(f.record().pendingRevocations, [{ installationId: oldId, scope: "a".repeat(64) }]);
assert.doesNotMatch(JSON.stringify(f.record()), /credentials|provider|opaque|fixture-auth/);
});
test("failed old-authority retries retain intent while other queued authorities are never contacted", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable(); await f.controller.disable();
const queue = [{ installationId: randomUUID(), scope: "a".repeat(64) }, { installationId: randomUUID(), scope: "b".repeat(64) }];
f.options.write({ ...f.record(), pendingRevocations: queue });
const attempted = []; f.api.revoke = async id => { attempted.push(id); throw new Error("offline"); };
await f.controller.refresh();
assert.deepEqual(attempted, [queue[0].installationId]); assert.deepEqual(f.record().pendingRevocations, queue);
await f.controller.enable();
assert.equal(f.record().enabled, true); assert.deepEqual(f.record().pendingRevocations, queue);
});
test("bounded revocation queue never silently evicts cleanup and gives actionable opt-in recovery", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable(); await f.controller.disable();
const queue = Array.from({ length: MAX_AGENT_PUSH_REVOCATIONS }, () => ({ installationId: randomUUID(), scope: "b".repeat(64) }));
f.options.write({ ...f.record(), pendingRevocations: queue });
await f.controller.refresh();
assert.equal(f.controller.state().canEnable, false); assert.match(f.controller.state().message, /Sign in to a previous server/);
const count = f.calls.filter(([name]) => name === "register").length;
await f.controller.enable();
assert.deepEqual(f.record().pendingRevocations, queue); assert.equal(f.calls.filter(([name]) => name === "register").length, count);
assert.ok(parseAgentPushConsent(JSON.stringify(f.record())));
assert.equal(parseAgentPushConsent(JSON.stringify({ ...f.record(), pendingRevocations: [...queue, queue[0]] })), null);
assert.equal(parseAgentPushConsent(JSON.stringify({ ...f.record(), pendingRevocations: [{ installationId: queue[0].installationId, scope: "raw-token" }] })), null);
f.options.scope = async () => "b".repeat(64);
await f.controller.refresh();
assert.deepEqual(f.record().pendingRevocations, []); assert.equal(f.controller.state().canEnable, true);
});
test("in-flight cleanup cannot overwrite a newer tab's consent revision", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const entered = deferred(), release = deferred();
let reads = 0;
f.options.scope = async () => {
if (++reads === 2) { entered.resolve(); await release.promise; }
return "a".repeat(64);
};
const disabling = f.controller.disable(); await entered.promise;
const newer = { ...f.record(), installationId: randomUUID(), revision: randomUUID(), enabled: true, revocationPending: false };
f.options.write(newer); release.resolve(); await disabling;
assert.deepEqual(f.record(), newer);
});
test("principal scope changing while permission is pending prevents registration", async () => {
const prompt = deferred(), f = fixture({ platform: { requestPermission: () => prompt.promise } });
await f.controller.refresh(); const enabling = f.controller.enable();
f.options.scope = async () => "b".repeat(64);
prompt.resolve("granted"); await enabling;
assert.equal(f.record().enabled, false); assert.ok(!f.calls.some(([name]) => name === "subscribe" || name === "register"));
});
test("authority changing during config fetch cannot enable against a mixed identity snapshot", async () => {
const f = fixture({ api: { config: async () => { f.options.scope = async () => "b".repeat(64); return configuration(); } } });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.controller.state().canEnable, false); assert.equal(f.record(), null); assert.deepEqual(f.calls, []);
});
test("same backend/token with a replaced source invalidates persisted consent on reload and requires a new opt-in", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const old = structuredClone(f.record());
f.setConfig({ ...configuration(), sourceId: replacementSourceId });
const reloaded = createAgentPushController(f.options);
await reloaded.refresh(); await reloaded.refresh();
assert.equal(f.record().enabled, false); assert.equal(f.record().sourceId, sourceId);
assert.equal(f.calls.filter(([name]) => name === "register").length, 1, "source replacement cannot reuse old consent");
assert.ok(f.calls.some(([name, id]) => name === "revoke" && id === old.installationId));
assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
await reloaded.enable();
assert.equal(f.record().enabled, true); assert.equal(f.record().sourceId, replacementSourceId);
assert.equal(f.record().scope, old.scope); assert.notEqual(f.record().installationId, old.installationId);
assert.equal(f.calls.filter(([name]) => name === "permission").length, 2, "replacement requires another explicit permission gesture");
});
test("null or invalid source prevents new opt-in even when transport is configured", async () => {
for (const unknown of [null, undefined, "not-a-source-uuid"]) {
const f = fixture(); f.setConfig({ ...configuration(), sourceId: unknown });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.controller.state().canEnable, false); assert.equal(f.record(), null);
assert.deepEqual(f.calls, [], "unknown source must not request permission, subscribe or register");
}
});
test("losing a known source retires old consent and never resumes it automatically", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const oldId = f.record().installationId;
f.setConfig({ ...configuration(), sourceId: null });
await f.controller.refresh();
assert.equal(f.record().enabled, false); assert.equal(f.controller.state().canEnable, false);
assert.ok(f.calls.some(([name, id]) => name === "revoke" && id === oldId));
f.setConfig(configuration()); await f.controller.refresh();
assert.equal(f.record().enabled, false); assert.equal(f.calls.filter(([name]) => name === "register").length, 1);
});
test("legacy or malformed source bindings retain installation identity for retirement, not re-enrollment", async () => {
for (const invalid of [undefined, null, "invalid-source"]) {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const oldId = f.record().installationId;
const stored = parseAgentPushConsent(JSON.stringify({ ...f.record(), sourceId: invalid }));
assert.equal(stored.installationId, oldId); assert.equal(stored.sourceId, undefined);
f.options.write(stored);
await createAgentPushController(f.options).refresh();
assert.equal(f.record().enabled, false); assert.equal(f.calls.filter(([name]) => name === "register").length, 1);
assert.ok(f.calls.some(([name, id]) => name === "revoke" && id === oldId));
}
});
test("source change while permission is pending prevents subscription and retires the incomplete intent", async () => {
const prompt = deferred(), f = fixture({ platform: { requestPermission: () => prompt.promise } });
await f.controller.refresh(); const enabling = f.controller.enable();
const oldId = f.record().installationId;
f.setConfig({ ...configuration(), sourceId: replacementSourceId });
f.setPermission("granted"); prompt.resolve("granted"); await enabling;
assert.equal(f.record().enabled, false); assert.equal(f.record().sourceId, sourceId);
assert.ok(!f.calls.some(([name]) => name === "subscribe" || name === "register"));
assert.ok(f.calls.some(([name, id]) => name === "revoke" && id === oldId));
});
test("source change while subscribe is pending unsubscribes without registering", async () => {
const entered = deferred(), release = deferred();
const f = fixture({ platform: { subscribe: async () => {
entered.resolve(); await release.promise;
const subscription = f.makeSubscription(); f.setSubscription(subscription); return subscription;
} } });
await f.controller.refresh(); const enabling = f.controller.enable(); await entered.promise;
f.setConfig({ ...configuration(), sourceId: replacementSourceId }); release.resolve(); await enabling;
assert.equal(f.record().enabled, false); assert.equal(f.calls.filter(([name]) => name === "register").length, 0);
assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
});
test("source change during initial registration rolls back instead of persisting enabled consent", async () => {
const entered = deferred(), release = deferred(), registered = [], retired = [];
const f = fixture({ api: {
register: async id => { registered.push(id); entered.resolve(); await release.promise; },
revoke: async id => { retired.push(id); },
} });
await f.controller.refresh(); const enabling = f.controller.enable(); await entered.promise;
f.setConfig({ ...configuration(), sourceId: replacementSourceId }); release.resolve(); await enabling;
assert.equal(f.record().enabled, false); assert.equal(f.record().sourceId, sourceId);
assert.deepEqual(retired, registered); assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
});
test("source change during automatic rotation registration retires the source-bound installation", async () => {
const f = fixture(); await f.controller.refresh(); await f.controller.enable();
const oldId = f.record().installationId, entered = deferred(), release = deferred();
f.setSubscription(f.makeSubscription("https://push.example.test/rotated"));
f.api.register = async id => { assert.equal(id, oldId); entered.resolve(); await release.promise; };
const refreshing = f.controller.refresh(); await entered.promise;
f.setConfig({ ...configuration(), sourceId: replacementSourceId }); release.resolve(); await refreshing;
assert.equal(f.record().enabled, false); assert.ok(f.calls.some(([name, id]) => name === "revoke" && id === oldId));
});
test("source verification failure after registration fails closed and rolls back", async () => {
const f = fixture({ api: { register: async () => { f.api.config = async () => { throw new Error("source unavailable"); }; } } });
await f.controller.refresh(); await f.controller.enable();
assert.equal(f.record().enabled, false); assert.ok(f.calls.some(([name]) => name === "revoke"));
assert.ok(f.calls.some(([name]) => name === "unsubscribe"));
});
test("attention is per-tab, ordered, focused while idle, and recent input persists until acknowledged", async () => {
const rows = [], failure = deferred(); let focus = true;
const a = createAgentPresenceReporter({ snapshot: () => ({ visibility: "visible", focused: focus }), send: async value => { rows.push(["a", value]); } });
const b = createAgentPresenceReporter({ snapshot: () => ({ visibility: "hidden", focused: true }), send: async value => { rows.push(["b", value]); } });
await a.report(); await b.report();
assert.equal(rows[0][1].focused, true); assert.equal(rows[0][1].interacted, false);
assert.equal(rows[1][1].focused, false); assert.equal(rows[1][1].sequence, 1);
a.interact(); focus = false; await a.report(); await a.report();
assert.equal(rows[2][1].interacted, true); assert.equal(rows[2][1].focused, false);
assert.equal(rows[3][1].interacted, false); assert.equal(rows[3][1].sequence, 3);
let attempts = 0;
const c = createAgentPresenceReporter({ snapshot: () => ({ visibility: "visible", focused: false }), send: async value => {
assert.equal(value.interacted, true); if (++attempts === 1) return failure.promise;
} });
c.interact(); const report = c.report(); failure.reject(new Error("offline")); await assert.rejects(report); await c.report();
const id = randomUUID(), frame = JSON.parse(agentAuthFrame("fixture-token", "2", id));
assert.equal(frame.clientKind, "web"); assert.equal(frame.presenceSessionId, id);
assert.equal(JSON.parse(agentAuthFrame(null, "0", "not-an-id")).clientKind, undefined);
});
test("read frames validate source/cursor and preserve existing read semantics", () => {
const event = parseAgentRoomEvent(JSON.stringify({ type: "read_state", cursor: "9", updatedAt: null, sourceId }));
assert.equal(applyAgentEvent(createAgentRoomState(), event).readCursor, "9");
for (const cursor of ["-1", "1e5", "9007199254740992", "1".repeat(200)]) {
assert.equal(agentPushCursor(cursor), false);
assert.throws(() => parseAgentRoomEvent(JSON.stringify({ type: "read_state", cursor, updatedAt: null, sourceId })));
}
assert.throws(() => parseAgentRoomEvent(JSON.stringify({ type: "read_state", cursor: "1", updatedAt: null, sourceId: "https://evil.test" })));
assert.equal(parseAgentPushConsent('{"enabled":true}'), null);
assert.throws(() => parseAgentPushConfig({ ...configuration(), policy: { presenceHeartbeatMs: 1, presenceLeaseMs: 60000 } }));
});
test("API keeps credentials in headers, validates receipts, handles 204 and redacts provider bodies", async () => {
const id = randomUUID(), requests = []; let mode = "config";
const api = new AgentPushClient({ baseUrl: "/api", token: () => "fixture-bearer", fetch: async (url, init) => {
requests.push({ url, init });
if (mode === "revoke") return new Response(null, { status: 204 });
if (mode === "fail") return new Response(JSON.stringify({ error: "private-token-and-endpoint" }), { status: 500 });
if (mode === "register") return Response.json({ installationId: id, transport: "web", enabled: true, version: 1 });
if (mode === "read") return Response.json({ cursor: "0008", sourceId });
return Response.json(configuration());
} });
await api.config(); mode = "register"; await api.register(id, { endpoint: "https://provider.test/endpoint-secret" });
mode = "read"; assert.deepEqual(await api.readState(), { sourceId, cursor: "8" });
mode = "revoke"; await api.revoke(id);
mode = "fail"; await assert.rejects(api.revoke(id), error => !/private-token/.test(error.message));
for (const request of requests) {
assert.equal(request.init.headers.Authorization, "Bearer fixture-bearer");
assert.equal(request.init.cache, "no-store"); assert.equal(request.init.redirect, "error");
assert.doesNotMatch(request.url, /fixture-bearer|endpoint-secret/);
}
await assert.rejects(api.revoke("../../bad"));
const unauthenticated = new AgentPushClient({ baseUrl: "/api", token: () => null, fetch: () => { throw new Error("must not send"); } });
await assert.rejects(unauthenticated.config(), /Sign in/);
});
test("authority-guarded API refuses mismatched credentials and pins the captured bearer across async hashing", async () => {
const id = randomUUID(), requests = [], baseUrl = "https://sybil.example.test/api/";
const scope = await agentPushAuthorityScope(baseUrl, "original-fixture-token");
assert.equal(scope, await agentPushAuthorityScope("/api", "original-fixture-token", "https://sybil.example.test/"));
assert.match(scope, /^[a-f0-9]{64}$/); assert.equal(await agentPushAuthorityScope(baseUrl, null), null);
let token = "rotated-fixture-token";
const api = new AgentPushClient({ baseUrl, token: () => token, fetch: async (url, init) => {
requests.push({ url, init });
return init.method === "DELETE" ? new Response(null, { status: 204 }) : Response.json({ installationId: id, transport: "web", enabled: true, version: 1 });
} });
await assert.rejects(api.revoke(id, scope));
await assert.rejects(api.register(id, { endpoint: "https://provider.test/fixture" }, scope));
assert.deepEqual(requests, [], "wrong authority cannot send a mutation or falsely acknowledge cleanup");
token = "original-fixture-token";
const deleting = api.revoke(id, scope); token = "rotated-fixture-token"; await deleting;
token = "original-fixture-token";
const registering = api.register(id, { endpoint: "https://provider.test/fixture" }, scope); token = "rotated-fixture-token"; await registering;
assert.equal(requests.length, 2);
assert.ok(requests.every(({ init }) => init.headers.Authorization === "Bearer original-fixture-token"));
assert.ok(requests.every(({ url }) => !url.includes("fixture-token")));
});