// Advertise a fake punktfunk/1 host over real mDNS (NWListener) and assert HostDiscovery's // NWBrowser finds it, resolves an address+port, and parses the TXT (id / pair / fp). This // exercises the whole client discovery path on the loopback/LAN; it self-skips if the test // environment blocks Bonjour (sandboxed CI without local-network access). import Network import PunktfunkKit import XCTest final class HostDiscoveryTests: XCTestCase { func testFindsAdvertisedHost() async throws { let serviceName = "PunktfunkTest-\(UUID().uuidString.prefix(8))" let uniqueid = "test-\(UUID().uuidString)" var txt = NWTXTRecord() txt["proto"] = "punktfunk/1" txt["fp"] = String(repeating: "ab", count: 32) // 64 hex chars, like a real cert SHA-256 txt["pair"] = "required" txt["id"] = uniqueid let listener = try NWListener(using: .udp) listener.service = NWListener.Service( name: String(serviceName), type: "_punktfunk._udp", txtRecord: txt) // The resolver opens a throwaway UDP flow to read the resolved endpoint — accept and // drop it so it doesn't linger. listener.newConnectionHandler = { connection in connection.cancel() } listener.start(queue: .global()) defer { listener.cancel() } let discovery = await HostDiscovery() await discovery.start() defer { Task { await discovery.stop() } } // Poll up to ~10s for the advert to be browsed AND resolved. var found: DiscoveredHost? let deadline = Date().addingTimeInterval(10) while Date() < deadline { if let host = await discovery.hosts.first(where: { $0.name == String(serviceName) }) { found = host break } try await Task.sleep(nanoseconds: 200_000_000) } guard let host = found else { throw XCTSkip("mDNS discovery unavailable in this environment (no local network).") } XCTAssertEqual(host.id, uniqueid, "the stable mDNS id should key the host") XCTAssertTrue(host.requiresPairing, "pair=required must surface as requiresPairing") XCTAssertEqual(host.fingerprintHex, String(repeating: "ab", count: 32)) XCTAssertFalse(host.host.isEmpty, "a resolved address is required to connect") XCTAssertGreaterThan(host.port, 0, "a resolved port is required to connect") } }