sync.test.ts

3.8 KB · raw

1import { beforeEach, describe, expect, it, vi } from "vitest";23type MessageListener = (message: unknown) => void;4type DisconnectListener = () => void;56function installChromeMock() {7  const messageListeners: MessageListener[] = [];8  const disconnectListeners: DisconnectListener[] = [];9  const port = {10    postMessage: vi.fn(),11    disconnect: vi.fn(() => {12      for (const listener of disconnectListeners) listener();13    }),14    onMessage: {15      addListener: vi.fn((listener: MessageListener) => {16        messageListeners.push(listener);17      }),18    },19    onDisconnect: {20      addListener: vi.fn((listener: DisconnectListener) => {21        disconnectListeners.push(listener);22      }),23    },24  };25  const connectNative = vi.fn(() => port);2627  vi.stubGlobal("chrome", {28    runtime: {29      connectNative,30    },31  });3233  return {34    connectNative,35    port,36    emit(message: unknown) {37      for (const listener of messageListeners) listener(message);38    },39    disconnect() {40      for (const listener of disconnectListeners) listener();41    },42  };43}4445describe("sync native host client", () => {46  beforeEach(() => {47    vi.resetModules();48    vi.useRealTimers();49    vi.unstubAllGlobals();50  });5152  it("requests the native identity using the Rust host protocol", async () => {53    const chromeMock = installChromeMock();54    const sync = await import("./sync");5556    const promise = sync.getNativeIdentity();57    expect(chromeMock.connectNative).toHaveBeenCalledWith("com.peachpasswords.sync");58    expect(chromeMock.port.postMessage).toHaveBeenCalledWith({ type: "GET_IDENTITY" });5960    chromeMock.emit({ type: "IDENTITY", public_key_hex: "aa".repeat(32) });61    await expect(promise).resolves.toBe("aa".repeat(32));62  });6364  it("sends trusted-device control messages with snake_case fields", async () => {65    const chromeMock = installChromeMock();66    const sync = await import("./sync");6768    const promise = sync.trustDevice("bb".repeat(32), "June's iPhone");69    expect(chromeMock.port.postMessage).toHaveBeenCalledWith({70      type: "TRUST_DEVICE",71      public_key_hex: "bb".repeat(32),72      device_name: "June's iPhone",73    });7475    chromeMock.emit({ type: "TRUST_DEVICE_OK" });76    await expect(promise).resolves.toBeUndefined();77  });7879  it("maps native peer discovery events for subscribers", async () => {80    const chromeMock = installChromeMock();81    const sync = await import("./sync");82    const handler = vi.fn();8384    sync.connectSyncHost();85    sync.onPeerDiscovered(handler);86    chromeMock.emit({87      type: "PEER_DISCOVERED",88      peer: {89        host: "100.64.0.2",90        port: 49422,91        x25519_public_key: "remote-key",92        device_id: "device-id",93        name: "June's MacBook",94        pairing: true,95      },96    });9798    expect(handler).toHaveBeenCalledWith({99      host: "100.64.0.2",100      port: 49422,101      x25519PublicKey: "remote-key",102      deviceId: "device-id",103      name: "June's MacBook",104      pairing: true,105    });106  });107108  it("never sends private key material in CONNECT requests", async () => {109    const chromeMock = installChromeMock();110    const sync = await import("./sync");111112    const promise = sync.connectToPeer("100.64.0.2", 49422, "remote-key");113    expect(chromeMock.port.postMessage).toHaveBeenCalledWith({114      type: "CONNECT",115      host: "100.64.0.2",116      port: 49422,117      remote_x25519_public_key: "remote-key",118      local_x25519_private_key: "",119    });120121    chromeMock.emit({ type: "CONNECTED", connection_id: "conn-1" });122    await expect(promise).resolves.toBe("conn-1");123  });124125  it("rejects pending requests when the native host disconnects", async () => {126    const chromeMock = installChromeMock();127    const sync = await import("./sync");128129    const promise = sync.getNativeStatus();130    chromeMock.disconnect();131132    await expect(promise).rejects.toThrow("sync native host disconnected");133  });134});