import { beforeEach, describe, expect, it, vi } from "vitest"; const mocks = vi.hoisted(() => ({ openSocket: vi.fn(), getLocalIdentity: vi.fn(), verifyPassword: vi.fn(), getRootKeyHex: vi.fn(), upsert: vi.fn(), pushes: [] as Array<{ type: string; payload: unknown }>, triggerSync: vi.fn(), })); vi.mock("../shared/pairing-dial", () => ({ openRelayPairingSocket: mocks.openSocket, })); vi.mock("../shared/noise-js", () => ({ noiseNKResponder: vi.fn(async () => ({ handshakeHash: new Uint8Array(32).fill(7), encrypt: (bytes: Uint8Array) => bytes, decrypt: (bytes: Uint8Array) => bytes, close: vi.fn(), })), })); vi.mock("../shared/rendezvous", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, generatePairingRendezvousToken: () => "AAECAwQFBgcICQoLDA0ODw", }; }); vi.mock("./code-pairing-bg", () => ({ productionCodePairingDeps: () => ({ getLocalIdentity: mocks.getLocalIdentity, }), })); vi.mock("./relay-settings", () => ({ getRelaySettings: vi.fn(async () => ({ relayUrl: "wss://relay.peachpasswords.com", lanOnly: false, })), })); vi.mock("./license", () => ({ isProUser: vi.fn(async () => false), getLicenseKey: vi.fn(async () => undefined), })); vi.mock("./vault", () => ({ isUnlocked: vi.fn(() => true), verifyMainPassword: mocks.verifyPassword, getRootKeyHex: mocks.getRootKeyHex, })); vi.mock("../shared/sync-identity", () => ({ computeVaultId: vi.fn(async () => "b".repeat(64)), })); vi.mock("./paired-devices", () => ({ upsertPairedDevice: mocks.upsert, })); vi.mock("./sync-trigger", () => ({ triggerSyncIfReachable: mocks.triggerSync, })); import { cancelRelayAdoptionSource, confirmRelayAdoptionSource, startRelayAdoptionSource, } from "./relay-adoption-source-bg"; const encode = (value: unknown) => new TextEncoder().encode(JSON.stringify(value)); function socketWithFrames(frames: unknown[]) { const queue = frames.map(encode); return { send: vi.fn(), receive: vi.fn(async () => { const next = queue.shift(); if (!next) return new Promise(() => undefined); return next; }), close: vi.fn(), closedBeforeFrame: vi.fn(() => false), }; } async function waitForPush(type: string): Promise { await vi.waitFor(() => { expect(mocks.pushes.some((message) => message.type === type)).toBe(true); }); } beforeEach(() => { mocks.pushes.length = 0; mocks.openSocket.mockReset(); mocks.getLocalIdentity.mockReset(); mocks.verifyPassword.mockReset(); mocks.getRootKeyHex.mockReset(); mocks.upsert.mockReset(); mocks.triggerSync.mockReset(); mocks.triggerSync.mockResolvedValue(undefined); mocks.getLocalIdentity.mockResolvedValue({ identity: { deviceId: "browser-source", deviceName: "June's Browser", deviceType: "extension", longTermPublicKeyHex: "a".repeat(64), vaultId: "b".repeat(64), }, privateKey: new Uint8Array(32).fill(1), publicKey: new Uint8Array(32).fill(2), }); mocks.getRootKeyHex.mockReturnValue("c".repeat(64)); mocks.upsert.mockResolvedValue(undefined); Object.defineProperty(globalThis, "chrome", { configurable: true, value: { runtime: { sendMessage(message: { type: string; payload: unknown }) { mocks.pushes.push(message); return Promise.resolve(); }, }, }, }); }); describe("relay adoption source background boundary", () => { it("does not access the root for a wrong password and only grants after both confirmations", async () => { const socket = socketWithFrames([ { type: "PEACH_HELLO", token: "AAECAwQFBgcICQoLDA0ODw", }, { type: "ADOPT_REQUEST", device_id: "request-only-label", device_name: "June's phone", device_type: "android", }, { type: "ADOPT_CONFIRM", confirmed: true }, { type: "ADOPT_IDENTITY", device_id: "android-receiver", device_name: "June's phone", device_type: "android", long_term_public_key_hex: "d".repeat(64), }, ]); mocks.openSocket.mockResolvedValue(socket); mocks.verifyPassword .mockResolvedValueOnce(false) .mockResolvedValueOnce(true); const started = await startRelayAdoptionSource(); expect(started.invitation).toMatch(/^peach-pair-v1:/); await waitForPush("ADOPTION_SOURCE_SAS"); expect(mocks.getRootKeyHex).not.toHaveBeenCalled(); await expect(confirmRelayAdoptionSource("wrong")).resolves.toEqual({ valid: false, }); expect(mocks.getRootKeyHex).not.toHaveBeenCalled(); await expect(confirmRelayAdoptionSource("correct")).resolves.toEqual({ valid: true, }); await waitForPush("ADOPTION_SOURCE_RESULT"); expect(mocks.getRootKeyHex).toHaveBeenCalled(); expect(mocks.upsert).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "android-receiver", relayUrl: "wss://relay.peachpasswords.com", })); expect(mocks.pushes).toContainEqual(expect.objectContaining({ type: "ADOPTION_SOURCE_RESULT", payload: expect.objectContaining({ status: "complete" }), })); expect(mocks.triggerSync).toHaveBeenCalledWith({ reason: "manual", force: true, }); }); it("fails closed on cancellation without reading or sending the root", async () => { const socket = socketWithFrames([ { type: "PEACH_HELLO", token: "AAECAwQFBgcICQoLDA0ODw", }, { type: "ADOPT_REQUEST", device_id: "request-only-label", device_name: "New phone", device_type: "android", }, ]); mocks.openSocket.mockResolvedValue(socket); await startRelayAdoptionSource(); await waitForPush("ADOPTION_SOURCE_SAS"); cancelRelayAdoptionSource(); await waitForPush("ADOPTION_SOURCE_RESULT"); expect(mocks.getRootKeyHex).not.toHaveBeenCalled(); expect(mocks.upsert).not.toHaveBeenCalled(); expect(mocks.pushes).toContainEqual({ type: "ADOPTION_SOURCE_RESULT", payload: { status: "error", error: "adoption_rejected" }, }); }); });