import { expect, test } from 'bun:test'; import { Hono } from 'hono'; import { privateAddress, sanitize, extractArticle, registerReaderRoutes } from './reader.js'; const PAGE = `Psalm 23 study

Psalm 23 study

By Ana

${'

The Lord is my shepherd; I shall not want. He makes me lie down in green pastures. He leads me beside still waters.

'.repeat(8)}

Read the next part

bad
`; test('private and special addresses are refused', () => { for (const ip of ['127.0.0.1', '10.8.0.1', '192.168.1.2', '172.20.0.1', '169.254.169.254', '100.64.0.1', '::1', 'fd00::1', '::ffff:10.0.0.1', '0.0.0.0']) expect(privateAddress(ip)).toBe(true); for (const ip of ['93.184.216.34', '1.1.1.1', '2606:4700::1111']) expect(privateAddress(ip)).toBe(false); }); test('sanitize keeps reading tags, makes links absolute and strips scripts/handlers', () => { const out = sanitize('

Hi linkkept text

', 'https://site.example/a/b'); expect(out).toMatch(/]*href="https:\/\/site\.example\/x"[^>]*>link<\/a>/); expect(out).toContain('rel="noopener noreferrer nofollow"'); expect(out).toContain('src="https://site.example/a/i.png"'); expect(out).toContain('kept text'); for (const bad of ['script', 'onclick', 'onerror', 'iframe', 'style=', 'custom']) expect(out).not.toContain(bad); }); test('extractArticle finds the article and drops unsafe parts', () => { const a = extractArticle(PAGE, 'https://bible.example/psalm-23'); expect(a.title).toBe('Psalm 23 study'); expect(a.content).toContain('The Lord is my shepherd'); expect(a.content).toContain('href="https://bible.example/next"'); for (const bad of [' { const present = { ABCDEF: 'https://bible.example/psalm-23', PRIVAT: 'http://10.0.0.5/admin', HOPPER: 'https://hop.example/' }; const resolve = async (h) => [{ address: h === 'inside.example' ? '192.168.0.9' : '93.184.216.34' }]; const fetchImpl = async (url) => url.startsWith('https://hop.example') ? new Response('', { status: 302, headers: { location: 'http://inside.example/secret' } }) : new Response(PAGE, { headers: { 'content-type': 'text/html; charset=utf-8' } }); const app = new Hono(); registerReaderRoutes(app, { presentUrlOf: (code) => present[code], fetchImpl, resolve }); const get = async (code) => { const r = await app.request(`/api/party/${code}/reader`); return { status: r.status, json: await r.json() }; }; expect((await get('ZZZZZZ')).status).toBe(404); const ok = await get('abcdef'); expect(ok.json).toMatchObject({ ok: true, title: 'Psalm 23 study' }); expect((await get('PRIVAT')).json.error).toMatch(/public internet/); expect((await get('HOPPER')).json.error).toMatch(/public internet/); });