import { describe, it, expect } from "vitest"; import { decodeClientFrame, encodeServerFrame, encodeBinaryUpdate, encodeBinaryAwareness, StrikeTracker, WS_CLOSE, MAX_FRAME_BYTES, type DecodedFrame, } from "../src/protocol.js"; import { BINARY_KIND, type ServerFrame } from "@factory/shared/realtime/frames"; function text(o: unknown): string { return JSON.stringify(o); } describe("decodeClientFrame — text frames", () => { it("accepts a sync.request (full sync — no stateVector)", () => { const r = decodeClientFrame(text({ type: "sync.request" })); expect(r).toEqual({ ok: true, frame: { kind: "text", frame: { type: "sync.request" } } }); }); it("accepts a sync.request with base64 stateVector + lastSeq (resume paths)", () => { const r = decodeClientFrame(text({ type: "sync.request", stateVector: "AAAA", lastSeq: 42 })); expect(r).toMatchObject({ ok: true, frame: { kind: "text", frame: { type: "sync.request", stateVector: "AAAA", lastSeq: 42 } }, }); }); it("accepts a ping with ts (and optional lastSeq)", () => { const r = decodeClientFrame(text({ type: "ping", ts: 1700000000000 })); expect(r).toMatchObject({ ok: true, frame: { kind: "text", frame: { type: "ping", ts: 1700000000000 } }, }); const r2 = decodeClientFrame(text({ type: "ping", ts: 1, lastSeq: 7 })); expect(r2).toMatchObject({ ok: true, frame: { kind: "text", frame: { lastSeq: 7 } } }); }); it("accepts an outbox.pending with a non-negative integer count", () => { const r = decodeClientFrame(text({ type: "outbox.pending", count: 3 })); expect(r).toMatchObject({ ok: true, frame: { kind: "text", frame: { type: "outbox.pending", count: 3 } }, }); }); it("accepts a subscribe for a page channel (control room, TASK-0092 6b)", () => { const r = decodeClientFrame(text({ type: "subscribe", channel: "page:pg_1" })); expect(r).toEqual({ ok: true, frame: { kind: "text", frame: { type: "subscribe", channel: "page:pg_1" } }, }); }); it("accepts a subscribe for a workspace channel (accepted, no frames ride it yet)", () => { const r = decodeClientFrame(text({ type: "subscribe", channel: "workspace:wsp_1" })); expect(r).toMatchObject({ ok: true, frame: { kind: "text", frame: { type: "subscribe", channel: "workspace:wsp_1" } }, }); }); it("accepts an unsubscribe for a page channel", () => { const r = decodeClientFrame(text({ type: "unsubscribe", channel: "page:pg_1" })); expect(r).toMatchObject({ ok: true, frame: { kind: "text", frame: { type: "unsubscribe", channel: "page:pg_1" } }, }); }); it("rejects a subscribe with an unknown channel kind as invalid_frame (not unknown_frame)", () => { // `type` is a known client frame; the `channel` value is bad → invalid_frame. expect(decodeClientFrame(text({ type: "subscribe", channel: "bogus:pg_1" }))).toEqual({ ok: false, code: "invalid_frame", }); }); it("rejects a subscribe with an empty id as invalid_frame", () => { expect(decodeClientFrame(text({ type: "subscribe", channel: "page:" }))).toEqual({ ok: false, code: "invalid_frame", }); }); it("accepts a presence frame (cursor move, TASK-0093) and a presence-leave", () => { const r = decodeClientFrame(text({ type: "presence", cursor: { blockId: "b3", offset: 4 } })); expect(r).toEqual({ ok: true, frame: { kind: "text", frame: { type: "presence", cursor: { blockId: "b3", offset: 4 } } }, }); expect(decodeClientFrame(text({ type: "presence-leave" }))).toEqual({ ok: true, frame: { kind: "text", frame: { type: "presence-leave" } }, }); }); it("rejects a presence frame with a bad payload as invalid_frame (not unknown_frame)", () => { // `type` is a known client frame; the payload is bad → invalid_frame // (the strike matrix's known-type branch — a client can't dodge // strikes by sending a presence with a malformed cursor). expect( decodeClientFrame(text({ type: "presence", cursor: { blockId: "", offset: 4 } })), ).toEqual({ ok: false, code: "invalid_frame" }); }); it("strips a client-supplied identity from a presence frame (Zod non-strict: unknown keys are dropped)", () => { // The wire schema is non-strict: an extra `user` key is STRIPPED by // the parse (accepted + removed) — the server stamps the identity // from the handshake regardless, so impersonation is impossible // either way (the R2 security contract). const r = decodeClientFrame(text({ type: "presence", user: { id: "u-evil" }, cursor: null })); expect(r.ok).toBe(true); if (!r.ok) return; expect(r.frame.frame).toEqual({ type: "presence", cursor: null }); }); it("rejects non-JSON input as invalid_json", () => { expect(decodeClientFrame("this is not json")).toEqual({ ok: false, code: "invalid_json" }); }); it("rejects a wholly-unknown type as unknown_frame", () => { expect(decodeClientFrame(text({ type: "bogus", docId: "d1" }))).toEqual({ ok: false, code: "unknown_frame", }); }); it("rejects a known type with a bad payload as invalid_frame", () => { // ping without the required ts → invalid_frame (the type is known). expect(decodeClientFrame(text({ type: "ping" }))).toEqual({ ok: false, code: "invalid_frame" }); }); it("rejects a sync.request with a negative lastSeq as invalid_frame", () => { expect(decodeClientFrame(text({ type: "sync.request", lastSeq: -1 }))).toEqual({ ok: false, code: "invalid_frame", }); }); it("rejects an outbox.pending with a non-integer count as invalid_frame", () => { expect(decodeClientFrame(text({ type: "outbox.pending", count: 1.5 }))).toEqual({ ok: false, code: "invalid_frame", }); }); }); describe("decodeClientFrame — binary frames", () => { it("accepts a 0x01 doc update (payload = the raw Yjs bytes after the prefix)", () => { const bytes = new Uint8Array([0x01, 0x00, 0x49, 0x7c]); // prefix + fake yjs const r = decodeClientFrame(bytes) as { ok: true; frame: DecodedFrame }; expect(r.ok).toBe(true); if (r.frame.kind === "binary") { expect(r.frame.binary).toBe("update"); expect(Array.from(r.frame.bytes)).toEqual([0x00, 0x49, 0x7c]); } else { throw new Error("expected a binary frame"); } }); it("accepts a 0x02 awareness update", () => { const r = decodeClientFrame(new Uint8Array([0x02, 0x01])); expect(r).toMatchObject({ ok: true }); }); it("rejects an empty binary frame as bad_binary_kind", () => { expect(decodeClientFrame(new Uint8Array([]))).toEqual({ ok: false, code: "bad_binary_kind" }); }); it("rejects an unknown binary kind (0x7f) as bad_binary_kind", () => { expect(decodeClientFrame(new Uint8Array([0x7f, 0x01]))).toEqual({ ok: false, code: "bad_binary_kind", }); }); }); describe("encodeServerFrame / encodeBinaryUpdate / encodeBinaryAwareness", () => { it("serializes a server frame to JSON", () => { const frame: ServerFrame = { type: "error", code: "forbidden", message: "no edit permission" }; expect(JSON.parse(encodeServerFrame(frame)) as ServerFrame).toEqual(frame); }); it("prefixes a binary update with 0x01", () => { const payload = new Uint8Array([0xaa, 0xbb]); const out = encodeBinaryUpdate(payload); expect(out[0]).toBe(BINARY_KIND.Update); expect(Array.from(out.subarray(1))).toEqual([0xaa, 0xbb]); }); it("prefixes a binary awareness update with 0x02", () => { const out = encodeBinaryAwareness(new Uint8Array([0x01])); expect(out[0]).toBe(BINARY_KIND.Awareness); expect(out.byteLength).toBe(2); }); }); describe("StrikeTracker (the 10-strike policy, R3)", () => { function clock() { let t = 0; return { now: () => t, advance: (ms: number) => (t += ms) }; } it("violates exactly at the limit within the window", () => { const c = clock(); const tracker = new StrikeTracker({ limit: 3, windowMs: 10_000, now: c.now }); expect(tracker.record().violated).toBe(false); expect(tracker.record().violated).toBe(false); expect(tracker.record()).toEqual({ violated: true, strikes: 3 }); }); it("a strike that falls out of the window no longer counts", () => { const c = clock(); const tracker = new StrikeTracker({ limit: 3, windowMs: 1000, now: c.now }); tracker.record(); // t=0 c.advance(900); // t=900 tracker.record(); // still inside the window: strikes=[0,900] expect(tracker.count()).toBe(2); c.advance(200); // t=1100 — the t=0 strike is now older than 1000 ms const third = tracker.record(); expect(third.violated).toBe(false); expect(third.strikes).toBe(2); // only the t=900 + t=1100 strikes remain }); it("count reports the in-window strikes", () => { const c = clock(); const tracker = new StrikeTracker({ limit: 10, windowMs: 10_000, now: c.now }); tracker.record(); tracker.record(); tracker.record(); expect(tracker.count()).toBe(3); tracker.reset(); expect(tracker.count()).toBe(0); }); }); describe("close codes + frame cap (frozen contract)", () => { it("WS_CLOSE carries the documented 40xx values", () => { expect(WS_CLOSE).toEqual({ ProtocolOrHeartbeat: 4000, Unauthorized: 4001, Forbidden: 4002, RateOrPolicy: 4008, PayloadTooLarge: 4009, ServerShutdown: 4010, }); }); it("the inbound frame cap is 256 KB", () => { expect(MAX_FRAME_BYTES).toBe(256 * 1024); }); });