const { test } = require('node:test'); const assert = require('node:assert'); const DoodstreamUploader = require('../lib/doodstream-upload'); // The CDN hands back an XFileSharing form. `fn` is the filecode, `st` is the // status ("OK" on success, an error string when the backend refuses the file). // These tests pin the parse/error behaviour of _parseUploadResponse without // touching the network — _fetch is stubbed to return the upload_result page. function cdnForm({ fn = '', st = 'OK' } = {}) { return `
`; } const EMPTY_RESULT = ''; const LINK_RESULT = (code) => ``; function uploaderWithResult(resultHtml) { const up = new DoodstreamUploader(); up._lastUploadUrl = 'https://cdn.example/upload/01'; // Stub the second-step submit so no real request goes out. up._fetch = async () => ({ text: async () => resultHtml }); return up; } test('rejected file: empty fn + non-OK st surfaces the real status', async () => { const up = uploaderWithResult(EMPTY_RESULT); await assert.rejects( () => up._parseUploadResponse(cdnForm({ fn: '', st: 'Error: file already exists' })), (err) => { assert.match(err.message, /lehnt Datei ab/); assert.match(err.message, /file already exists/); return true; } ); }); test('empty fn + st OK: generic error still reports st, fn-state and CDN node', async () => { const up = uploaderWithResult(EMPTY_RESULT); await assert.rejects( () => up._parseUploadResponse(cdnForm({ fn: '', st: 'OK' })), (err) => { assert.match(err.message, /kein Filecode/); assert.match(err.message, /st=OK/); assert.match(err.message, /fehlt\/leer/); assert.match(err.message, /cdn\.example/); return true; } ); }); test('valid fn but empty result page: still resolves via fn (no regression)', async () => { const up = uploaderWithResult(EMPTY_RESULT); const res = await up._parseUploadResponse(cdnForm({ fn: '7mnp8xna3123', st: 'OK' })); assert.equal(res.file_code, '7mnp8xna3123'); assert.equal(res.download_url, 'https://doodstream.com/d/7mnp8xna3123'); }); test('happy path: link in result page wins', async () => { const up = uploaderWithResult(LINK_RESULT('jjsuhr931ds9')); const res = await up._parseUploadResponse(cdnForm({ fn: 'jjsuhr931ds9', st: 'OK' })); assert.equal(res.file_code, 'jjsuhr931ds9'); }); // --- _fetch: transient network blips on the small requests self-heal --- test('_fetch retries a transient network failure then succeeds', async () => { const up = new DoodstreamUploader(); const origFetch = globalThis.fetch; let calls = 0; globalThis.fetch = async () => { calls++; if (calls === 1) throw new TypeError('fetch failed'); return { status: 200, headers: { getSetCookie: () => [], get: () => null }, text: async () => 'ok' }; }; try { const res = await up._fetch('https://example.test/x'); assert.equal(calls, 2); // failed once, retried, succeeded assert.equal(await res.text(), 'ok'); } finally { globalThis.fetch = origFetch; } }); // --- _getUploadServer: discovery must never fall back to a hardcoded node --- function fakeRes(body, { status = 200, ctype = 'text/html' } = {}) { return { status, headers: { get: (h) => (h.toLowerCase() === 'content-type' ? ctype : null) }, text: async () => body }; } test('getUploadServer: returns JSON result when present', async () => { const up = new DoodstreamUploader(); up._fetch = async (url) => { assert.match(url, /op=upload_server/); return fakeRes(JSON.stringify({ result: 'https://node42.cloudatacdn.com/upload/01' }), { ctype: 'application/json' }); }; assert.equal(await up._getUploadServer(), 'https://node42.cloudatacdn.com/upload/01'); }); test('getUploadServer: falls back to srv_url in upload-page HTML', async () => { const up = new DoodstreamUploader(); up._fetch = async (url) => { if (/op=upload_server/.test(url)) return fakeRes('not json'); return fakeRes(''); }; assert.equal(await up._getUploadServer(), 'https://node7.cloudatacdn.com/upload/01'); }); test('getUploadServer: parses current form-action node and refreshes sess_id from the same page', async () => { const up = new DoodstreamUploader(); up.sessId = 'stale-from-login'; up._fetch = async (url) => { if (/op=upload_server/.test(url)) return fakeRes('not json'); return fakeRes(''); }; const url = await up._getUploadServer(); assert.equal(url, 'https://n9.cloudatacdn.com/upload/01?FRESH123'); assert.equal(up.sessId, 'FRESH123'); // critical: form-field token must match the node URL token }); test('getUploadServer: un-escapes & in the form-action query string', async () => { const up = new DoodstreamUploader(); up._fetch = async (url) => { if (/op=upload_server/.test(url)) return fakeRes('not json'); return fakeRes(''); }; assert.equal(await up._getUploadServer(), 'https://n9.cloudatacdn.com/upload/01?a=1&b=2'); }); test('getUploadServer: throws (no silent dead fallback) when discovery fails', async () => { const up = new DoodstreamUploader(); up._fetch = async () => fakeRes('login required', { status: 200 }); await assert.rejects( () => up._getUploadServer(), (err) => { assert.match(err.message, /konnte Upload-Server nicht ermitteln/); assert.doesNotMatch(err.message, /tr1128ve\.cloudatacdn\.com/); // never the hardcoded node return true; } ); });