1import { beforeEach, describe, expect, it, vi } from "vitest";23import {4 dialPairingChannel,5 dialRelayPairingChannel,6 type PairingSocket,7} from "./pairing-dial";8import { encodePairingCode } from "./pairing-code";9import { createPairingInvitation } from "./pairing-invitation";1011const enc = (o: unknown) => new TextEncoder().encode(JSON.stringify(o));12const dec = (b: Uint8Array) => JSON.parse(new TextDecoder().decode(b));1314const LISTENER_EPHEMERAL_HEX = "a".repeat(64);15const CODE = encodePairingCode("192.168.4.77", 51555, Uint8Array.of(0xde, 0xad, 0xbe, 0xef));16const RELAY_TOKEN = "AAECAwQFBgcICQoLDA0ODw";17const RELAY_INVITATION = createPairingInvitation(18 "wss://relay.example.test/base",19 RELAY_TOKEN,20);2122const FAKE_HANDSHAKE_HASH = Uint8Array.from([0x0f, 0xba, 0xb8, 0xcd, ...new Array(28).fill(0)]);23242526vi.mock("./noise-js", async (orig) => {27 const actual = await orig<typeof import("./noise-js")>();28 return {29 ...actual,30 noiseNKInitiator: vi.fn(async () => ({31 encrypt: (pt: Uint8Array) => pt,32 decrypt: (ct: Uint8Array) => ct,33 handshakeHash: FAKE_HANDSHAKE_HASH,34 })),35 };36});373839function fakeSocket(inbound: Array<Uint8Array | null>) {40 const sent: Uint8Array[] = [];41 let closed = false;42 let lastWasClose = false;43 const queue = [...inbound];44 const sock: PairingSocket = {45 send(bytes) { sent.push(bytes); },46 async receive() {47 const next = queue.shift();48 if (next === undefined || next === null) {49 closed = true;50 lastWasClose = true;51 throw new Error("socket closed");52 }53 lastWasClose = false;54 return next;55 },56 close() { closed = true; },57 closedBeforeFrame() { return lastWasClose; },58 };59 return { sock, sent, isClosed: () => closed };60}6162function ackThen(...rest: Array<Uint8Array | null>) {63 return fakeSocket([64 enc({ type: "PEACH_HELLO_ACK", ephemeralPublicKeyHex: LISTENER_EPHEMERAL_HEX }),65 ...rest,66 ]);67}6869beforeEach(() => { vi.clearAllMocks(); });7071describe("dialPairingChannel", () => {72 it("dials the coded endpoint", async () => {73 const openSocket = vi.fn(async () => ackThen().sock);7475 const result = await dialPairingChannel(CODE, { openSocket });7677 expect(result.ok).toBe(true);78 expect(openSocket).toHaveBeenCalledWith("192.168.4.77", 51555);79 });8081 it("presents the token as hex and returns the shared SAS", async () => {82 const s = ackThen();83 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });8485 expect(dec(s.sent[0])).toEqual({ type: "PEACH_HELLO", token: "deadbeef" });86 expect(result.ok && result.dialled.sas).toBe("895245");87 expect(result.ok && result.dialled.host).toBe("192.168.4.77");88 expect(result.ok && result.dialled.rendezvousSecretHex).toBe(89 "a8977cd86e944e72e9cb569dbba00a7b0abbf7f456c745299aa8f66157859143",90 );91 });9293 it("marks LAN adoption before Noise so the source can skip device-pair auth", async () => {94 const s = ackThen();95 const result = await dialPairingChannel(96 CODE,97 { openSocket: async () => s.sock },98 { flow: "adoption" },99 );100101 expect(result.ok).toBe(true);102 expect(dec(s.sent[0])).toEqual({103 type: "PEACH_HELLO",104 token: "deadbeef",105 flow: "adoption",106 });107 });108109 it("rejects an addressless legacy code before opening a socket", async () => {110 const openSocket = vi.fn(async () => ackThen().sock);111112 const result = await dialPairingChannel("123456", { openSocket });113114 expect(result).toEqual({ ok: false, error: "invalid_code" });115 expect(openSocket).not.toHaveBeenCalled();116 });117118 it("reads a silent close as invalid_code, not a transport failure", async () => {119 120 const s = fakeSocket([null]);121 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });122123 expect(result).toEqual({ ok: false, error: "invalid_code" });124 expect(s.isClosed()).toBe(true);125 });126127 it("treats a non-ACK first frame exactly like a wrong token", async () => {128 const s = fakeSocket([enc({ type: "SOMETHING_ELSE" })]);129 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });130131 expect(result).toEqual({ ok: false, error: "invalid_code" });132 });133134 it("rejects an ACK whose ephemeral key is not 32-byte hex", async () => {135 const s = fakeSocket([enc({ type: "PEACH_HELLO_ACK", ephemeralPublicKeyHex: "nope" })]);136 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });137138 expect(result).toEqual({ ok: false, error: "handshake_failed" });139 expect(s.isClosed()).toBe(true);140 });141142 it("reports no_host_found when the endpoint refuses the connection", async () => {143 const result = await dialPairingChannel(CODE, {144 openSocket: async () => { throw new Error("ECONNREFUSED"); },145 });146147 expect(result).toEqual({ ok: false, error: "no_host_found" });148 });149150 it("leaves the socket open on success so the caller owns its lifetime", async () => {151 const s = ackThen(enc({ hello: "world" }));152 const result = await dialPairingChannel(CODE, { openSocket: async () => s.sock });153 if (!result.ok) throw new Error("expected a dialled channel");154155 expect(s.isClosed()).toBe(false);156 await result.dialled.channel.send(enc({ type: "ANYTHING" }));157 158 expect(dec(s.sent[1])).toEqual({ type: "ANYTHING" });159 expect(dec(await result.dialled.channel.receive())).toEqual({ hello: "world" });160161 result.dialled.socket.close();162 expect(s.isClosed()).toBe(true);163 });164});165166describe("dialRelayPairingChannel", () => {167 it("occupies the invited relay room and runs the identical hello/NK path", async () => {168 const s = ackThen();169 const openSocket = vi.fn(async () => s.sock);170171 const result = await dialRelayPairingChannel(RELAY_INVITATION, { openSocket });172173 expect(result.ok).toBe(true);174 expect(openSocket).toHaveBeenCalledWith(175 `wss://relay.example.test/base/r/${RELAY_TOKEN}`,176 );177 expect(dec(s.sent[0])).toEqual({ type: "PEACH_HELLO", token: RELAY_TOKEN });178 expect(result.ok && result.dialled.relayUrl).toBe(179 "wss://relay.example.test/base",180 );181 expect(result.ok && result.dialled.host).toBe("relay.example.test");182 });183184 it("rejects a malformed invitation before opening a socket", async () => {185 const openSocket = vi.fn(async () => ackThen().sock);186 const result = await dialRelayPairingChannel("peach-pair-v1:not-valid", {187 openSocket,188 });189 expect(result).toEqual({ ok: false, error: "invalid_code" });190 expect(openSocket).not.toHaveBeenCalled();191 });192});