'use strict'; const assert = require('node:assert/strict'); const http = require('node:http'); const test = require('node:test'); const { createEzRemoteServerClient, buildDownloadRequest, normalizeDownloadStates, } = require('../../src/services/ezremote-server-client'); test('ezRemote request builder maps the documented client types and enforces safe PS5 roots', () => { const payload = buildDownloadRequest({ source: { protocol: 'smb', host: 'server.example.test', port: 445, path: '/Media/roms/PS5', username: 'chimera' }, sourcePath: 'CUSA00001/game.pkg', destinationPath: '/data/ezremote/CUSA00001.pkg', size: 123, id: 42, password: 'transient-only', }); assert.deepEqual(payload, { type: 2, url: 'smb://server.example.test:445', username: 'chimera', password: 'transient-only', src_path: '/Media/roms/PS5/CUSA00001/game.pkg', dest_path: '/data/ezremote/CUSA00001.pkg', size: 123, id: 42, }); assert.throws(() => buildDownloadRequest({ source: { protocol: 'smb', host: 'tower', port: 445 }, sourcePath: '../secret', destinationPath: '/data/ezremote/x', id: 1 }), /geen \.\./i); assert.throws(() => buildDownloadRequest({ source: { protocol: 'smb', host: 'tower', port: 445 }, sourcePath: 'game.pkg', destinationPath: '/system/unsafe.pkg', id: 1 }), /veilige ezRemote-roots/i); }); test('ezRemote client probes version, normalizes progress and submits the exact upstream JSON contract', async t => { const requests = []; const states = [{ path: '/data/ezremote/already.pkg', bytes_transfered: 50, file_size: 100, state: 2, timestamp: 7 }]; const server = http.createServer((req, res) => { if (req.url === '/version') return res.end('0.3.3'); if (req.url === '/get_download_state') { res.setHeader('Content-Type', 'application/json'); return res.end(JSON.stringify(states)); } if (req.url === '/download_url' && req.method === 'POST') { const chunks = []; req.on('data', chunk => chunks.push(chunk)); return req.on('end', () => { requests.push(JSON.parse(Buffer.concat(chunks).toString('utf8'))); res.statusCode = 200; res.end(''); }); } res.statusCode = 404; res.end(); }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); t.after(() => new Promise(resolve => server.close(resolve))); const values = new Map([['ftp.host', '127.0.0.1'], ['ezremote.serverPort', server.address().port]]); const client = createEzRemoteServerClient({ settings: { get: (key, fallback) => values.has(key) ? values.get(key) : fallback } }); const probe = await client.probe(); assert.equal(probe.reachable, true); assert.equal(probe.version, '0.3.3'); assert.equal(probe.downloads[0].state, 'resumed'); assert.equal(probe.downloads[0].percent, 50); assert.equal(probe.capabilities.cancel, false); const source = { protocol: 'ftp', host: 'nas.local', port: 21, path: '/roms', username: 'jens' }; const existing = await client.ensureDownload({ source, sourcePath: 'already.pkg', destinationPath: '/data/ezremote/already.pkg', id: 11 }); assert.equal(existing.submitted, false); assert.equal(requests.length, 0); const submitted = await client.submit({ source, sourcePath: 'new.pkg', destinationPath: '/data/ezremote/new.pkg', id: 12, password: 'runtime-secret' }); assert.equal(submitted.accepted, true); assert.equal(requests.length, 1); assert.deepEqual(requests[0], { type: 0, url: 'ftp://nas.local:21', username: 'jens', password: 'runtime-secret', src_path: '/roms/new.pkg', dest_path: '/data/ezremote/new.pkg', size: 0, id: 12, }); }); test('ezRemote download status normalizer bounds unknown console data', () => { const rows = normalizeDownloadStates([{ path: 'bad\0\npath', bytes_transfered: -1, file_size: 0, state: 99, timestamp: -2 }]); assert.equal(rows[0].path, 'bad path'); assert.equal(rows[0].transferred, 0); assert.equal(rows[0].state, 'unknown'); });