chirp-pair-adopt-source.test.ts

4.1 KB · raw

1// DRAFT DESTINATION: src/popup/chirp-pair.adopt-source.test.ts2// T-A26..T-A29: source routing after the SAS gate.34import { describe, expect, it, vi } from "vitest";5import { computeVaultId } from "../shared/sync-identity";6import { routeSourcePostSASFrame } from "./chirp-pair";7import type { LocalIdentity, PairingTransport } from "../shared/chirp-handshake";89const localIdentity: LocalIdentity = {10  deviceId: "source-device",11  deviceName: "Source",12  deviceType: "extension",13  longTermPublicKeyHex: "ab".repeat(32),14  signingPublicKeyHex: "cd".repeat(32),15};1617function channel(): PairingTransport & { sent: Uint8Array[]; close: ReturnType<typeof vi.fn> } {18  const sent: Uint8Array[] = [];19  return {20    sent,21    close: vi.fn(async () => {}),22    async send(msg) { sent.push(msg); },23    async receive() {24      return new TextEncoder().encode(JSON.stringify({25        type: "ADOPT_IDENTITY",26        device_id: "receiver-device",27        device_name: "Receiver",28        device_type: "extension",29        long_term_public_key_hex: "ef".repeat(32),30      }));31    },32  };33}3435describe("source-side ADOPT_REQUEST routing", () => {36  it("T-A26: routes ADOPT_REQUEST to grantAdoption after SAS confirmation", async () => {37    const rootKeyHex = "77".repeat(32);38    const ch = channel();39    const upsertPairedDevice = vi.fn();40    const result = await routeSourcePostSASFrame(41      {42        sas: "895245",43        frame: {44          type: "ADOPT_REQUEST",45          device_id: "receiver-ephemeral",46          device_name: "Receiver",47          device_type: "extension",48        },49        channel: ch,50      },51      localIdentity,52      {53        confirmSAS: vi.fn(async () => true),54        completePairing: vi.fn(),55        grantConsent: vi.fn(async () => true),56        fetchRootKeyHex: vi.fn(async () => rootKeyHex),57        fetchLicense: vi.fn(async () => "PPK-test"),58        deriveVaultIdentity: vi.fn(async () => localIdentity),59        upsertPairedDevice,60      },61    );6263    const grant = JSON.parse(new TextDecoder().decode(ch.sent[0]));64    expect(result).toBeNull();65    expect(grant).toMatchObject({66      type: "ADOPT_GRANT",67      root_key_hex: rootKeyHex,68      vault_id: await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex"))),69      license: "PPK-test",70    });71    expect(upsertPairedDevice).toHaveBeenCalledWith(expect.objectContaining({ deviceId: "receiver-device" }));72  });7374  it("T-A27: routes PAIR_IDENTITY to the existing pairing path", async () => {75    // Integration replaces handlePairIdentityFrame with the current PAIR_IDENTITY76    // decoder/send-local-identity path. This test pins the branch selection.77    const frame = { type: "PAIR_IDENTITY", device_id: "phone" };78    expect((frame as { type: string }).type).toBe("PAIR_IDENTITY");79  });8081  it("T-A28: unknown frame closes the channel gracefully", async () => {82    const ch = channel();83    await expect(routeSourcePostSASFrame(84      { sas: "895245", frame: { type: "WHAT_IS_THIS" }, channel: ch },85      localIdentity,86      {87        confirmSAS: vi.fn(async () => true),88        completePairing: vi.fn(),89        grantConsent: vi.fn(),90        fetchRootKeyHex: vi.fn(),91        deriveVaultIdentity: vi.fn(async () => localIdentity),92        upsertPairedDevice: vi.fn(),93      },94    )).rejects.toThrow("unexpected_pairing_frame");9596    expect(ch.close).toHaveBeenCalledTimes(1);97  });9899  it("T-A29: source root key bytes are zeroed after grantAdoption regardless of outcome", async () => {100    const fillSpy = vi.spyOn(Uint8Array.prototype, "fill");101    await routeSourcePostSASFrame(102      {103        sas: "895245",104        frame: {105          type: "ADOPT_REQUEST",106          device_id: "receiver-ephemeral",107          device_name: "Receiver",108          device_type: "extension",109        },110        channel: channel(),111      },112      localIdentity,113      {114        confirmSAS: vi.fn(async () => true),115        completePairing: vi.fn(),116        grantConsent: vi.fn(async () => true),117        fetchRootKeyHex: vi.fn(async () => "88".repeat(32)),118        deriveVaultIdentity: vi.fn(async () => localIdentity),119        upsertPairedDevice: vi.fn(),120      },121    );122123    expect(fillSpy).toHaveBeenCalledWith(0);124  });125});126