1import { describe, expect, it } from "vitest";23import { decodeSyncMessage, encodeSyncMessage, isSyncMessage } from "./sync-wire";4import type { SyncMessage } from "./sync-types";56describe("sync wire codec", () => {7 it("round-trips sync messages through base64url JSON", () => {8 const message: SyncMessage = {9 type: "SYNC_HELLO",10 deviceId: "phone-\u2603",11 clockMs: 1_778_000_000_000,12 vaultVersion: 4,13 };1415 const encoded = encodeSyncMessage(message);1617 expect(encoded).toMatch(/^[A-Za-z0-9_-]+$/);18 expect(encoded).not.toMatch(/[+/=]/);19 expect(decodeSyncMessage(encoded)).toEqual(message);20 });2122 it("round-trips manifest and encrypted entry payloads", () => {23 const messages: SyncMessage[] = [24 {25 type: "SYNC_MANIFEST",26 entries: [{ id: "entry-1", modifiedAt: 42, deletedAt: 45, hash: "abc123" }],27 },28 {29 type: "SYNC_ENTRIES",30 entries: [31 {32 id: "entry-1",33 modifiedAt: 42,34 ciphertext: "ciphertext",35 nonce: "nonce",36 tag: "tag",37 },38 ],39 },40 ];4142 for (const message of messages) {43 expect(decodeSyncMessage(encodeSyncMessage(message))).toEqual(message);44 }45 });4647 it("validates message-specific required fields", () => {48 expect(isSyncMessage({ type: "SYNC_REQUEST", ids: ["a", "b"] })).toBe(true);49 expect(isSyncMessage({ type: "PAIR_ACCEPT", edPublicKey: "key", name: "Laptop" })).toBe(50 true51 );52 expect(isSyncMessage({ type: "SYNC_REQUEST", ids: [7] })).toBe(false);53 expect(isSyncMessage({ type: "SYNC_HELLO", deviceId: "phone", clockMs: "now" })).toBe(54 false55 );56 expect(isSyncMessage({ type: "SYNC_MANIFEST", entries: [{ id: "a" }] })).toBe(false);57 expect(isSyncMessage({ type: "UNKNOWN" })).toBe(false);58 });5960 it("rejects malformed wire payloads with a stable error", () => {61 const malformedJson = btoa("{").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");62 const unknownType = btoa(JSON.stringify({ type: "BOGUS" }))63 .replace(/\+/g, "-")64 .replace(/\//g, "_")65 .replace(/=+$/g, "");6667 expect(() => decodeSyncMessage("@@@")).toThrow("invalid sync message");68 expect(() => decodeSyncMessage("A")).toThrow("invalid sync message");69 expect(() => decodeSyncMessage(malformedJson)).toThrow("invalid sync message");70 expect(() => decodeSyncMessage(unknownType)).toThrow("invalid sync message");71 });72});