Files
hub/tools/vbv-test.js

50 lines
7.9 KiB
JavaScript

// Verse By Verse Ministry: the study list is read from the ministry's category pages, lessons from a study's podcast feed, and audio and handouts are sent to the ministry's own media addresses only.
const http = require('http'), assert = require('assert'), fs = require('fs'), os = require('os'), path = require('path');
process.env.DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'nehub-vbv-'));
let port; const gif = Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64'), seen = [];
const card = (slug, title, n, who) => `<li class="study-item -card"><a class="link-wrapper js-study-item" href="/bible-studies/${slug}"><figure><div><img src="http://127.0.0.1:${port}/img/tiny.gif" srcset="http://127.0.0.1:${port}/img/tiny.gif, http://127.0.0.1:${port}/img/small.gif 2x" /></div></figure><div class="content-block"><h3 class="title">${title}</h3><h4 class="lesson-count">${n} Lessons</h4><p class="body-text -serif">${who}</p></div></a></li>`;
const fake = http.createServer((req, res) => {
const u = new URL(req.url, 'http://x'), p = u.pathname; seen.push(p);
const H = (t, b) => { res.writeHead(200, { 'content-type': t }); res.end(b); };
if (p === '/bible-studies/category/old-testament-books') return H('text/html', `<ul>${card('genesis-2011', 'Genesis', 109, 'Stephen Armstrong')}${card('ruth', 'Ruth &amp; Naomi', 4, 'Wes')}</ul>`);
if (p === '/bible-studies/category/new-testament-books') return H('text/html', `<ul>${card('jude', 'Jude', 2, 'Wesley Livingston')}${card('ruth', 'Ruth &amp; Naomi', 4, 'Wes')}</ul>`);
if (p.startsWith('/bible-studies/category/')) return H('text/html', '<ul></ul>');
if (p === '/bible-studies/jude/podcast.xml') return H('application/xml', `<rss><channel><title>Jude</title><itunes:author>Wesley Livingston</itunes:author><itunes:summary>A study of Jude &amp; more.</itunes:summary><itunes:image href="http://127.0.0.1:${port}/img/cover.gif" />
<item><title>1 Jude 1-3</title><link>http://127.0.0.1:${port}/lessons/jude-lesson-1</link><description>Contend for the Faith</description><itunes:subtitle>Opening study</itunes:subtitle><enclosure url="http://127.0.0.1:${port}/media/1.mp3" length="5" type="audio/mpeg" /><pubDate>Sun, 29 Aug 2021 06:00:00 -0500</pubDate></item>
<item><title>2 Jude 4-8</title><link>http://127.0.0.1:${port}/lessons/jude-lesson-2</link><description>False teachers</description><enclosure url="https://evil.example.com/2.mp3" length="5" type="audio/mpeg" /></item>
<item><title>3 Jude 9-16</title><link>http://127.0.0.1:${port}/lessons/jude-lesson-3</link><description>Woe</description><enclosure url="http://127.0.0.1:${port}/media/3.mp3" length="5" type="audio/mpeg" /></item></channel></rss>`);
if (p === '/bible-studies/empty/podcast.xml') return H('application/xml', '<rss><channel><title>Empty</title></channel></rss>');
if (p === '/bible-studies/empty') return H('text/html', `<html><head><title>Empty Study | Ministry</title><meta property="og:image" content="http://127.0.0.1:${port}/img/cover.gif"></head><body><h1>Empty Study</h1><ul class="lesson-list"><li class="lesson -x"><a class="lesson-link" href="/lessons/empty-1"><span class="lesson-number">1A</span><p class="body-text -light">Chapter 1:1-5</p></a></li><li class="lesson"><a class="lesson-link" href="/lessons/empty-2"><span class="lesson-number">1B</span><p class="body-text -light">Chapter 1:6-9</p></a></li></ul></body></html>`);
if (p === '/lessons/empty-1') return H('text/html', `<audio src="http://127.0.0.1:${port}/media/e1.mp3"></audio>`);
if (p === '/lessons/empty-2') return H('text/html', '<video src="x.mp4"></video>');
if (p === '/lessons/jude-lesson-1') return H('text/html', `<a href="http://127.0.0.1:${port}/media/notes1.pdf">Notes</a>`);
if (p === '/lessons/jude-lesson-3') return H('text/html', '<p>No handout</p>');
if (p.startsWith('/img/')) { res.writeHead(200, { 'content-type': 'image/gif' }); return res.end(gif); }
res.writeHead(404); res.end();
}).listen(0, () => {
port = fake.address().port; process.env.VBV_URL = 'http://127.0.0.1:' + port;
const vbv = require('../vbv'), users = require('../users'), me = users.create({ name: 'Ann', role: 'admin', handle: 'YQ' }), bob = users.create({ name: 'Bob', role: 'user', tabs: ['podcast'], handle: 'Yg' });
const hub = http.createServer((req, res) => users.runAs(req.headers['x-user'] === 'b' ? bob : me, () => vbv.handle(req, res))).listen(0, async () => {
const base = 'http://127.0.0.1:' + hub.address().port + '/api/vbv', call = (p, o = {}) => fetch(base + p, { redirect: 'manual', ...o }), j = r => r.json();
try {
let r = await j(await call('/studies')); assert.deepEqual(r.studies.map(s => s.slug).sort(), ['genesis-2011', 'jude', 'ruth']); const ruth = r.studies.find(s => s.slug === 'ruth');
assert.equal(ruth.title, 'Ruth & Naomi'); assert.deepEqual(ruth.categories.sort(), ['new-testament-books', 'old-testament-books'], 'a study in two categories is listed once'); assert.equal(r.studies.find(s => s.slug === 'genesis-2011').lessons, 109); assert.match(r.studies[0].cover, /^\/api\/vbv\/cover\?u=/);
assert.deepEqual((await j(await call('/studies?category=old-testament-books&q=gen'))).studies.map(s => s.slug), ['genesis-2011']);
r = await j(await call('/study/jude')); assert.equal(r.teacher, 'Wesley Livingston'); assert.equal(r.description, 'A study of Jude & more.'); assert.equal(r.lessons.length, 2, 'a lesson whose audio is on another site is dropped'); assert.equal(r.lessons[0].title, 'Jude 1-3', 'the lesson number in the feed title is not repeated');
assert.ok(!JSON.stringify(r).includes('/media/'), 'recording addresses are not sent to the browser'); assert.equal(r.lessons[1].title, 'Jude 9-16');
let a = await call('/audio/jude/0'); assert.equal(a.status, 302); assert.equal(a.headers.get('location'), `http://127.0.0.1:${port}/media/1.mp3`); assert.equal((await call('/audio/jude/5')).status, 404);
a = await call('/notes/jude/0'); assert.equal(a.status, 302); assert.equal(a.headers.get('location'), `http://127.0.0.1:${port}/media/notes1.pdf`); assert.equal((await call('/notes/jude/1')).status, 404, 'a lesson with no handout says so');
r = await j(await call('/study/empty')); assert.equal(r.title, 'Empty Study'); assert.deepEqual(r.lessons.map(l => l.title), ['1A \u2013 Chapter 1:1-5', '1B \u2013 Chapter 1:6-9'], 'a study with an empty feed is read from its page');
a = await call('/audio/empty/0'); assert.equal(a.headers.get('location'), `http://127.0.0.1:${port}/media/e1.mp3`, "its recordings are found on each lesson's page when played"); assert.equal((await call('/audio/empty/1')).status, 404, 'a lesson with no recording says so');
a = await call('/notes/jude/1', { headers: { accept: 'text/html' } }); assert.equal(a.status, 404); assert.match(await a.text(), /no handout/, 'opened in a tab, a missing handout is explained in words');
assert.equal((await call('/study/..%2Fx')).status, 404); assert.equal((await call('/study/nothere')).status, 404);
a = await call('/cover?u=' + encodeURIComponent(`http://127.0.0.1:${port}/img/cover.gif`)); assert.equal(a.headers.get('content-type'), 'image/gif'); assert.equal((await call('/cover?u=' + encodeURIComponent('https://example.com/a.jpg'))).status, 404);
const post = o => call('/progress', { method: 'POST', body: JSON.stringify(o) }).then(j);
await post({ 'vbv:jude:0': { pos: 300, dur: 3000, t: 10 } }); assert.equal((await post({ 'vbv:jude:0': { pos: 5, dur: 3000, t: 3 } }))['vbv:jude:0'].pos, 300, 'the newest position wins'); assert.deepEqual(await j(await call('/progress', { headers: { 'x-user': 'b' } })), {}, "each person's places are their own");
const n = seen.length; await call('/studies'); await call('/study/jude'); assert.equal(seen.length, n, 'the ministry\'s pages are fetched once and kept for a while, not on every visit');
console.log('vbv test ok');
} catch (err) { console.error(err); process.exitCode = 1; }
hub.close(); fake.close();
});
});