import { describe, expect, it } from 'bun:test'; import { cliampUpgradeData, musicWebsocket } from './cliamp-ws'; // The player socket refuses a path before it spawns anything, so these two cases exercise the whole // server → handler → frame path without starting cliamp. Anything that would actually play needs a real // file and a real audio sink, so it is not tested here. function serveOnce() { const server = Bun.serve({ port: 0, hostname: '127.0.0.1', fetch(req, srv) { const url = new URL(req.url); const data = cliampUpgradeData(url.pathname, url.searchParams); if (data && srv.upgrade(req, { data })) return undefined as unknown as Response; return new Response('nope', { status: 400 }); }, websocket: musicWebsocket, }); return server; } function firstFrame(url: string): Promise { return new Promise((resolve, reject) => { const ws = new WebSocket(url); const timer = setTimeout(() => reject(new Error('no frame')), 3000); ws.addEventListener('message', (ev) => { clearTimeout(timer); ws.close(); resolve(String(ev.data)); }); ws.addEventListener('error', () => { clearTimeout(timer); reject(new Error('socket error')); }); }); } describe('cliamp player socket', () => { it('rejects a path that escapes the owner home', async () => { const server = serveOnce(); try { const frame = await firstFrame(`ws://127.0.0.1:${server.port}/cliamp/ws?files=../../etc/passwd`); expect(JSON.parse(frame)).toEqual({ type: 'output', data: '\r\n[Error] Invalid file path.\r\n' }); } finally { server.stop(true); } }); it('reports a missing files param instead of spawning', async () => { const server = serveOnce(); try { const frame = await firstFrame(`ws://127.0.0.1:${server.port}/cliamp/ws`); expect(JSON.parse(frame)).toEqual({ type: 'output', data: '\r\n[Error] No files specified.\r\n' }); } finally { server.stop(true); } }); it('routes only the two cliamp paths', () => { const q = new URLSearchParams(); expect(cliampUpgradeData('/cliamp/ws', q)).toEqual({ kind: 'player', files: '' }); expect(cliampUpgradeData('/cliamp/audio/ws', q)).toEqual({ kind: 'capture' }); expect(cliampUpgradeData('/stream', q)).toBeNull(); }); });