desktop-pairing-mdns.test.ts

3.2 KB · raw

1// Tests for mDNS discovery of the peach-sync desktop helper.23import { describe, expect, it, vi } from "vitest";4import {5  discoverPeachSync,6  parsePeachSyncService,7  type MdnsApi,8  type MdnsServiceRecord,9} from "./desktop-pairing-mdns";1011const VALID_RECORD: MdnsServiceRecord = {12  serviceName: "peach-sync-223e15d3._peach-sync._tcp.local",13  serviceHostPort: "kyoshi.local:8765",14  ipAddress: "192.168.4.230",15  serviceData: ["version=0.4.0", "id=223e15d3", "sync-port=8765", "pair-port=49142"],16};1718describe("parsePeachSyncService", () => {19  it("maps a full peach-sync record to ip + both ports + version", () => {20    expect(parsePeachSyncService(VALID_RECORD)).toEqual({21      ip: "192.168.4.230",22      syncPort: 8765,23      pairPort: 49142,24      version: "0.4.0",25    });26  });2728  it("ignores records for other service types", () => {29    expect(30      parsePeachSyncService({ ...VALID_RECORD, serviceName: "_googlecast._tcp.local" }),31    ).toBeNull();32  });3334  it("rejects a record with no usable IPv4 address", () => {35    expect(parsePeachSyncService({ ...VALID_RECORD, ipAddress: undefined })).toBeNull();36    expect(parsePeachSyncService({ ...VALID_RECORD, ipAddress: "kyoshi.local" })).toBeNull();37  });3839  it("falls back to the SRV port and default pair port when TXT omits them", () => {40    const svc = parsePeachSyncService({ ...VALID_RECORD, serviceData: [] });41    expect(svc).toEqual({ ip: "192.168.4.230", syncPort: 8765, pairPort: 49142, version: undefined });42  });4344  it("prefers the TXT sync-port over the SRV host:port", () => {45    const svc = parsePeachSyncService({46      ...VALID_RECORD,47      serviceHostPort: "kyoshi.local:9999",48      serviceData: ["sync-port=8765", "pair-port=49142"],49    });50    expect(svc?.syncPort).toBe(8765);51  });52});5354describe("discoverPeachSync", () => {55  it("resolves null when chrome.mdns is unavailable", async () => {56    await expect(discoverPeachSync({ mdns: null })).resolves.toBeNull();57  });5859  it("resolves the first peach-sync service from a service list", async () => {60    let captured: ((services: MdnsServiceRecord[]) => void) | null = null;61    const removeListener = vi.fn();62    const api: MdnsApi = {63      onServiceList: {64        addListener: (cb) => {65          captured = cb;66        },67        removeListener,68      },69      forceDiscovery: (cb) => cb(),70    };7172    const promise = discoverPeachSync({ mdns: api, timeoutMs: 1000 });73    captured!([{ serviceName: "_other._tcp.local" }, VALID_RECORD]);7475    await expect(promise).resolves.toEqual({76      ip: "192.168.4.230",77      syncPort: 8765,78      pairPort: 49142,79      version: "0.4.0",80    });81    expect(removeListener).toHaveBeenCalledTimes(1);82  });8384  it("resolves null and unsubscribes when nothing answers before the timeout", async () => {85    vi.useFakeTimers();86    try {87      const removeListener = vi.fn();88      const api: MdnsApi = {89        onServiceList: { addListener: () => undefined, removeListener },90      };91      const promise = discoverPeachSync({ mdns: api, timeoutMs: 3000 });92      await vi.advanceTimersByTimeAsync(3000);93      await expect(promise).resolves.toBeNull();94      expect(removeListener).toHaveBeenCalledTimes(1);95    } finally {96      vi.useRealTimers();97    }98  });99});