import { describe, expect, it } from "vitest"; import { buildAuthFailed, buildAuthRequest, buildAuthSuccess, buildDisconnectNotice, buildError, buildHeartbeat, buildHeartbeatAck, buildHello, buildHelloAck, buildPairConfirm, buildPairFailed, buildPairRequest, buildPairSuccess, buildRePairRequired, buildStatusUpdate, CodecError, decodeBuiltin, encodeBuiltin, encodeRewrittenRuleMessage, encodeRuleMessage, isBuiltinMessage, parseRewrittenRuleMessage, parseRuleMessage } from "../src/index.js"; describe("Protocol Codec - encodeBuiltin / decodeBuiltin", () => { it("encodes and decodes a hello envelope", () => { const envelope = buildHello( { identifier: "client-a", hasSecret: true, hasKeyPair: true, publicKey: "pk-test", protocolVersion: "1" }, { requestId: "req-001" } ); const encoded = encodeBuiltin(envelope); expect(encoded.startsWith("builtin::")).toBe(true); const decoded = decodeBuiltin(encoded); expect(decoded.type).toBe("hello"); expect(decoded.requestId).toBe("req-001"); expect(decoded.payload).toMatchObject({ identifier: "client-a", hasSecret: true, hasKeyPair: true, publicKey: "pk-test", protocolVersion: "1" }); }); it("encodes and decodes all builtin message types", () => { const testCases = [ { builder: buildHelloAck, payload: { identifier: "client-a", nextAction: "pair_required" as const } }, { builder: buildPairRequest, payload: { identifier: "client-a", expiresAt: 1_710_000_000, ttlSeconds: 300, adminNotification: "sent" as const, codeDelivery: "out_of_band" as const } }, { builder: buildPairConfirm, payload: { identifier: "client-a", pairingCode: "ABCD-1234-XYZ" } }, { builder: buildPairSuccess, payload: { identifier: "client-a", secret: "secret-xyz", pairedAt: 1_710_000_000 } }, { builder: buildPairFailed, payload: { identifier: "client-a", reason: "expired" as const } }, { builder: buildAuthRequest, payload: { identifier: "client-a", nonce: "RANDOM24CHARACTERSTRINGX", proofTimestamp: 1_710_000_000, signature: "sig-base64", publicKey: "pk-test" } }, { builder: buildAuthSuccess, payload: { identifier: "client-a", authenticatedAt: 1_710_000_000, status: "online" as const } }, { builder: buildAuthFailed, payload: { identifier: "client-a", reason: "invalid_signature" as const } }, { builder: buildRePairRequired, payload: { identifier: "client-a", reason: "nonce_collision" as const } }, { builder: buildHeartbeat, payload: { identifier: "client-a", status: "alive" as const } }, { builder: buildHeartbeatAck, payload: { identifier: "client-a", status: "online" as const } }, { builder: buildStatusUpdate, payload: { identifier: "client-a", status: "unstable" as const, reason: "heartbeat_timeout_7m" } }, { builder: buildDisconnectNotice, payload: { identifier: "client-a", reason: "heartbeat_timeout_11m" } }, { builder: buildError, payload: { code: "MALFORMED_MESSAGE" as const, message: "Invalid JSON" } } ]; for (const { builder, payload } of testCases) { const envelope = builder(payload as never, { requestId: "test-req" }); const encoded = encodeBuiltin(envelope); const decoded = decodeBuiltin(encoded); expect(decoded.type).toBe(envelope.type); expect(decoded.requestId).toBe("test-req"); expect(decoded.payload).toMatchObject(payload); } }); it("throws CodecError for malformed messages", () => { expect(() => decodeBuiltin("not-builtin::{")).toThrow(CodecError); expect(() => decodeBuiltin("builtin::not-json")).toThrow(CodecError); expect(() => decodeBuiltin("builtin::{}")).toThrow(CodecError); // missing type expect(() => decodeBuiltin("no-delimiter")).toThrow(CodecError); expect(() => decodeBuiltin("")).toThrow(CodecError); }); it("throws CodecError for invalid envelope input", () => { expect(() => encodeBuiltin(null as never)).toThrow(CodecError); expect(() => encodeBuiltin({} as never)).toThrow(CodecError); expect(() => encodeBuiltin({ type: 123 } as never)).toThrow(CodecError); }); }); describe("Protocol Codec - Rule Message Parsing", () => { it("parses rule messages correctly", () => { const parsed = parseRuleMessage("chat_sync::{\"body\":\"hello\"}"); expect(parsed.ruleIdentifier).toBe("chat_sync"); expect(parsed.content).toBe('{"body":"hello"}'); }); it("handles content containing :: delimiters", () => { const parsed = parseRuleMessage("rule::a::b::c"); expect(parsed.ruleIdentifier).toBe("rule"); expect(parsed.content).toBe("a::b::c"); }); it("throws CodecError for invalid rule messages", () => { expect(() => parseRuleMessage("no-delimiter")).toThrow(CodecError); expect(() => parseRuleMessage(":")).toThrow(CodecError); expect(() => parseRuleMessage("")).toThrow(CodecError); expect(() => parseRuleMessage("::content")).toThrow(CodecError); // empty identifier }); it("throws CodecError for reserved builtin identifier", () => { expect(() => parseRuleMessage("builtin::something")).toThrow( /reserved/ ); }); it("throws CodecError for invalid rule identifier characters", () => { expect(() => parseRuleMessage("rule with spaces::content")).toThrow(CodecError); // Note: special chars in content are allowed - only the rule identifier is validated expect(() => parseRuleMessage("rule::with::special!::chars")).not.toThrow(); // content can contain special chars }); it("isBuiltinMessage identifies builtin messages", () => { expect(isBuiltinMessage("builtin::{\"type\":\"hello\"}")).toBe(true); expect(isBuiltinMessage("rule::content")).toBe(false); expect(isBuiltinMessage("not-builtin::content")).toBe(false); expect(isBuiltinMessage("")).toBe(false); }); }); describe("Protocol Codec - Server Rewritten Messages", () => { it("parses rewritten messages with sender identifier", () => { const parsed = parseRewrittenRuleMessage("chat_sync::client-a::{\"body\":\"hello\"}"); expect(parsed.ruleIdentifier).toBe("chat_sync"); expect(parsed.senderIdentifier).toBe("client-a"); expect(parsed.messageContent).toBe('{"body":"hello"}'); expect(parsed.content).toBe("client-a::{\"body\":\"hello\"}"); }); it("handles complex content with multiple delimiters", () => { const parsed = parseRewrittenRuleMessage("rule::sender::a::b::c"); expect(parsed.ruleIdentifier).toBe("rule"); expect(parsed.senderIdentifier).toBe("sender"); expect(parsed.messageContent).toBe("a::b::c"); }); it("throws CodecError for malformed rewritten messages", () => { expect(() => parseRewrittenRuleMessage("no-delimiters")).toThrow(CodecError); expect(() => parseRewrittenRuleMessage("rule::only-one")).toThrow(/sender/); expect(() => parseRewrittenRuleMessage("::sender::content")).toThrow(/Empty/); // Note: "rule:::content" has empty sender which is caught by "missing sender identifier delimiter" check expect(() => parseRewrittenRuleMessage("rule:::content")).toThrow(/sender/); }); }); describe("Protocol Codec - encodeRuleMessage", () => { it("encodes rule messages correctly", () => { const encoded = encodeRuleMessage("chat_sync", '{"body":"hello"}'); expect(encoded).toBe('chat_sync::{"body":"hello"}'); }); it("throws CodecError for invalid rule identifiers", () => { expect(() => encodeRuleMessage("", "content")).toThrow(CodecError); expect(() => encodeRuleMessage("builtin", "content")).toThrow(/reserved/); expect(() => encodeRuleMessage("rule with spaces", "content")).toThrow(CodecError); expect(() => encodeRuleMessage("rule!", "content")).toThrow(CodecError); }); it("throws CodecError for non-string content", () => { expect(() => encodeRuleMessage("rule", null as never)).toThrow(CodecError); }); }); describe("Protocol Codec - encodeRewrittenRuleMessage", () => { it("encodes rewritten messages correctly", () => { const encoded = encodeRewrittenRuleMessage("chat_sync", "client-a", '{"body":"hello"}'); expect(encoded).toBe('chat_sync::client-a::{"body":"hello"}'); }); it("throws CodecError for reserved rule identifier", () => { expect(() => encodeRewrittenRuleMessage("builtin", "sender", "content")).toThrow(/reserved/); }); it("throws CodecError for empty identifiers", () => { expect(() => encodeRewrittenRuleMessage("", "sender", "content")).toThrow(CodecError); expect(() => encodeRewrittenRuleMessage("rule", "", "content")).toThrow(CodecError); }); });