Files
hub/tools/abdirect-test.js
2026-10-22 15:12:34 -04:00

37 lines
3.2 KiB
JavaScript

// Audiobooks straight from OpenAudible (OPENAUDIBLE_URL): its own username and password, byte ranges cut by the hub, positions kept by the hub per person.
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-abd-'));
process.env.DATA_DIR = dir; process.env.COOKIE_SECRET = 'abd-'.repeat(8);
const seen = [], AUDIO = Buffer.from('0123456789');
const oa = http.createServer((req, res) => {
seen.push({ u: req.url, auth: req.headers.authorization, range: req.headers.range });
if (req.headers.authorization !== 'Basic ' + Buffer.from('nate:oapw').toString('base64')) { res.writeHead(401); return res.end(); }
if (req.url === '/api/books') { res.writeHead(200, { 'content-type': 'application/json' }); return res.end('[{"id":"b1","title":"One"}]'); }
if (req.url === '/api/books/b1/files/b1.mp3') { res.writeHead(200, { 'content-type': 'audio/mpeg', 'content-length': AUDIO.length }); return res.end(AUDIO); } // ignores Range, like some servers
res.writeHead(404); res.end();
}).listen(0, async () => {
process.env.OPENAUDIBLE_URL = 'http://127.0.0.1:' + oa.address().port;
const users = require('../users'), abapi = require('../abapi'), me = { id: 'abd', name: 'N' };
const put = c => { fs.mkdirSync(path.join(dir, 'users', me.id), { recursive: true }); fs.writeFileSync(path.join(dir, 'users', me.id, 'settings.json'), JSON.stringify({ ab_creds: c })); };
put({ u: 'nate', p: 'oapw' });
const hub = http.createServer((req, res) => users.runAs(me, () => abapi.handle(req, res))).listen(0, async () => {
const base = 'http://127.0.0.1:' + hub.address().port + '/api/ab', get = (p, o) => fetch(base + p, o);
try {
let r = await get('/books'); assert.equal(r.status, 200); assert.equal((await r.json())[0].title, 'One');
r = await get('/books/b1/files/b1.mp3', { headers: { range: 'bytes=2-5' } }); assert.equal(r.status, 206); assert.equal(await r.text(), '2345'); assert.equal(r.headers.get('content-range'), 'bytes 2-5/10');
r = await get('/books/b1/files/b1.mp3', { headers: { range: 'bytes=7-' } }); assert.equal(await r.text(), '789');
r = await get('/books/b1/files/b1.mp3'); assert.equal(r.status, 200); assert.equal((await r.text()).length, 10);
const post = b => get('/progress', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(b) }).then(x => x.json());
await post({ b1: { pos: 50, dur: 100, t: 10 } }); let p = await post({ b1: { pos: 5, dur: 100, t: 5 }, b2: { pos: 1, dur: 9, t: 1 } });
assert.equal(p.b1.pos, 50, 'the newest position wins'); assert.equal(p.b2.pos, 1);
assert.equal((await (await get('/progress')).json()).b1.pos, 50);
put({ u: 'nate', p: 'wrong' });
r = await get('/books'); assert.equal(r.status, 502); assert.ok(/did not accept the username and password/.test((await r.json()).error));
put({});
r = await get('/books'); assert.equal(r.status, 503); assert.ok(/OpenAudible username and password in Settings/.test((await r.json()).error));
console.log('audiobooks direct ok');
} catch (e) { console.error(e); process.exitCode = 1; }
hub.close(); oa.close();
});
});