1import { describe, it, expect } from "vitest";2import {3 PAIRING_TOKEN_BYTES,4 encodePairingCode,5 formatPairingCode,6 parsePairingCode,7} from "./pairing-code";89const token4 = Uint8Array.of(0xde, 0xad, 0xbe, 0xef);1011describe("pairing-code", () => {12 13 14 it("golden vector: 192.168.1.42:49142 + deadbeef -> R2M0-2ANZ-YVFA-VFQF", () => {15 const code = encodePairingCode("192.168.1.42", 49142, token4);16 expect(code).toBe("R2M0-2ANZ-YVFA-VFQF");17 expect(parsePairingCode(code)).toEqual({18 ip: "192.168.1.42",19 port: 49142,20 tokenHex: "deadbeef",21 });22 });2324 it("is exactly 16 characters in 4 groups — the whole point of a 4-byte token", () => {25 const code = encodePairingCode("192.168.1.42", 49142, token4);26 expect(code.replace(/-/g, "")).toHaveLength(16);27 expect(code).toMatch(/^[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/);28 expect(PAIRING_TOKEN_BYTES).toBe(4);29 });3031 it("refuses to encode a token of any other size", () => {32 33 34 for (const n of [3, 5, 6, 8, 10]) {35 expect(() => encodePairingCode("192.168.1.42", 49142, new Uint8Array(n))).toThrow(/exactly 4 bytes/);36 }37 });3839 it("still decodes an older 8-byte-token code so a stale peer can pair", () => {40 41 expect(parsePairingCode("1800-43XF-HPH0-2EP9-A3PR-7P0")).toEqual({42 ip: "10.0.2.15",43 port: 44941,44 tokenHex: "a2013ac950ed83d8",45 });46 });4748 it("preserves the full port range", () => {49 for (const port of [1, 8765, 49142, 65535]) {50 const code = encodePairingCode("192.168.4.230", port, token4);51 expect(parsePairingCode(code)?.port).toBe(port);52 }53 });5455 it("folds the ambiguous glyphs I, L and O on the way in", () => {56 const code = encodePairingCode("192.168.1.42", 49142, token4);57 const ambiguous = code.replace(/0/g, "O").replace(/1/g, "I");58 expect(parsePairingCode(ambiguous)).toEqual(parsePairingCode(code));59 });6061 it("accepts a code typed without dashes, in lowercase, with stray spaces", () => {62 const code = encodePairingCode("192.168.1.42", 49142, token4);63 const sloppy = ` ${code.replace(/-/g, " ").toLowerCase()} `;64 expect(parsePairingCode(sloppy)).toEqual(parsePairingCode(code));65 });6667 it("rejects codes that are too short to carry a token", () => {68 69 expect(parsePairingCode("ABCDEFGHJK")).toBeNull();70 expect(parsePairingCode("")).toBeNull();71 expect(parsePairingCode("---")).toBeNull();72 });7374 it("rejects characters outside Crockford's alphabet", () => {75 76 expect(parsePairingCode("UUUU-UUUU-UUUU-UUUU")).toBeNull();77 expect(parsePairingCode("A74H-48J3-KI32-OEP!")).toBeNull();78 });7980 it("rejects a zero port", () => {81 82 83 expect(parsePairingCode("0000-0000-0000-0000")).toBeNull();84 });8586 it("refuses to encode an out-of-range address, port, or short token", () => {87 expect(() => encodePairingCode("192.168.1.256", 49142, token4)).toThrow();88 expect(() => encodePairingCode("192.168.1", 49142, token4)).toThrow();89 expect(() => encodePairingCode("192.168.1.42", 0, token4)).toThrow();90 expect(() => encodePairingCode("192.168.1.42", 70000, token4)).toThrow();91 expect(() => encodePairingCode("192.168.1.42", 49142, Uint8Array.of(1, 2, 3))).toThrow();92 });9394 it("regroups partial input as the user types", () => {95 expect(formatPairingCode("a74h48")).toBe("A74H-48");96 expect(formatPairingCode("A74H-48J3-KI32-OEP4")).toBe("A74H-48J3-KI32-OEP4");97 expect(formatPairingCode("a74h 48j3")).toBe("A74H-48J3");98 expect(formatPairingCode("")).toBe("");99 });100});