1234import { 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 76 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