1import { describe, it, expect } from "vitest";2import { encodePairingCode, formatPairingCode, parsePairingCode } from "./pairing-code";34const token4 = Uint8Array.of(0xde, 0xad, 0xbe, 0xef);5const token8 = Uint8Array.of(0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef);67describe("pairing-code", () => {8 it("round-trips a 4-byte token as exactly 16 characters", () => {9 const code = encodePairingCode("192.168.1.42", 49142, token4);10 expect(code.replace(/-/g, "")).toHaveLength(16);11 expect(code).toMatch(/^[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}-[0-9A-Z]{4}$/);12 expect(parsePairingCode(code)).toEqual({13 ip: "192.168.1.42",14 port: 49142,15 tokenHex: "deadbeef",16 });17 });1819 it("round-trips an 8-byte token without a coordinated length change", () => {20 const code = encodePairingCode("10.0.0.1", 8765, token8);21 expect(parsePairingCode(code)).toEqual({22 ip: "10.0.0.1",23 port: 8765,24 tokenHex: "0123456789abcdef",25 });26 });2728 it("preserves the full port range", () => {29 for (const port of [1, 8765, 49142, 65535]) {30 const code = encodePairingCode("192.168.4.230", port, token4);31 expect(parsePairingCode(code)?.port).toBe(port);32 }33 });3435 it("folds the ambiguous glyphs I, L and O on the way in", () => {36 const code = encodePairingCode("192.168.1.42", 49142, token4);37 const ambiguous = code.replace(/0/g, "O").replace(/1/g, "I");38 expect(parsePairingCode(ambiguous)).toEqual(parsePairingCode(code));39 });4041 it("accepts a code typed without dashes, in lowercase, with stray spaces", () => {42 const code = encodePairingCode("192.168.1.42", 49142, token4);43 const sloppy = ` ${code.replace(/-/g, " ").toLowerCase()} `;44 expect(parsePairingCode(sloppy)).toEqual(parsePairingCode(code));45 });4647 it("rejects codes that are too short to carry a token", () => {48 49 expect(parsePairingCode("ABCDEFGHJK")).toBeNull();50 expect(parsePairingCode("")).toBeNull();51 expect(parsePairingCode("---")).toBeNull();52 });5354 it("rejects characters outside Crockford's alphabet", () => {55 56 expect(parsePairingCode("UUUU-UUUU-UUUU-UUUU")).toBeNull();57 expect(parsePairingCode("A74H-48J3-KI32-OEP!")).toBeNull();58 });5960 it("rejects a zero port", () => {61 62 63 expect(parsePairingCode("0000-0000-0000-0000")).toBeNull();64 });6566 it("refuses to encode an out-of-range address, port, or short token", () => {67 expect(() => encodePairingCode("192.168.1.256", 49142, token4)).toThrow();68 expect(() => encodePairingCode("192.168.1", 49142, token4)).toThrow();69 expect(() => encodePairingCode("192.168.1.42", 0, token4)).toThrow();70 expect(() => encodePairingCode("192.168.1.42", 70000, token4)).toThrow();71 expect(() => encodePairingCode("192.168.1.42", 49142, Uint8Array.of(1, 2, 3))).toThrow();72 });7374 it("regroups partial input as the user types", () => {75 expect(formatPairingCode("a74h48")).toBe("A74H-48");76 expect(formatPairingCode("A74H-48J3-KI32-OEP4")).toBe("A74H-48J3-KI32-OEP4");77 expect(formatPairingCode("a74h 48j3")).toBe("A74H-48J3");78 expect(formatPairingCode("")).toBe("");79 });80});