import { describe, expect, it } from 'bun:test'; import { PeripheralTwinInstance } from '../peripheral-twin'; /** * Regression test for the peripheral WebRTC TURN gap. * * PeripheralTwinInstance.getWebRTCManager() once constructed the manager with no * options, so it never received an iceServersProvider — peripheral-attached media * (cameras/sensors) and data channels silently fell back to static STUN and could * not traverse a symmetric/CGNAT path. The fix mirrors PhyHubClient: inject a * provider that fetches short-lived TURN credentials via getIceServers('media'). */ describe('PeripheralTwinInstance — TURN ICE provider injection', () => { it('injects an iceServersProvider that fetches media TURN credentials', async () => { const iceServersResult = { iceServers: [{ urls: ['turn:relay:3478'] }], ttlSeconds: 3600 }; const getIceServersCalls: Array = []; const fakePhyHubClient = { getIceServers: async (useCase?: 'media' | 'data') => { getIceServersCalls.push(useCase); return iceServersResult; }, }; const instance = new PeripheralTwinInstance(fakePhyHubClient as never, 'twin-1'); // Stub the seams initialize() would otherwise populate. (instance as unknown as { messaging: unknown }).messaging = { emit() {}, on() {}, to: () => ({ emit() {} }) }; (instance as unknown as { webrtcTransport: unknown }).webrtcTransport = { twinId: 'twin-1', sendMessage: async () => {}, subscribe: async () => {}, onMessage: () => {}, offMessage: () => {}, }; const manager = (instance as unknown as { getWebRTCManager: () => unknown }).getWebRTCManager(); const provider = (manager as { iceServersProvider?: () => Promise }).iceServersProvider; expect(typeof provider).toBe('function'); const resolved = await provider!(); expect(resolved).toEqual(iceServersResult); // Must request the 'media' use case (1h-floor TTL safe for media + data). expect(getIceServersCalls).toEqual(['media']); }); });