import test from 'node:test'; import assert from 'node:assert/strict'; import { WikipediaArticleSource } from '../server/wiki.ts'; function page(pageid: number, patch: Record = {}) { return { pageid, ns: 0, title: `Article ${pageid}`, extract: `An encyclopedia article with enough explanatory text for a useful collectible card. ${pageid}.`, lastrevid: pageid + 100, thumbnail: { source: `https://upload.wikimedia.org/wikipedia/commons/${pageid}.jpg` }, categories: [{ title: 'Category:Science' }], ...patch }; } function json(value: unknown, headers: Record = {}) { return new Response(JSON.stringify(value), { headers: { 'Content-Type': 'application/json', ...headers } }); } function mockFetch(handler: (url: URL, init: RequestInit) => Response | Promise) { const calls: { url: URL; init: RequestInit }[] = []; const fetcher: typeof fetch = async (input, init) => { const url = new URL(String(input)); calls.push({ url, init: init! }); return handler(url, init!); }; return { fetcher, calls }; } test('random article lookup retries batches with no pictures and selects a pictured page', async () => { let attempt = 0; const { fetcher, calls } = mockFetch(() => { attempt++; return attempt === 1 ? json({ query: { pages: [page(10, { thumbnail: undefined }), page(11, { thumbnail: undefined })] } }) : json({ query: { pages: [page(12)] } }); }); const article = await new WikipediaArticleSource(fetcher).random(); assert.equal(attempt, 2); assert.equal(article.pageId, 12); assert.equal(article.source, 'live'); assert.equal(article.image, 'https://upload.wikimedia.org/wikipedia/commons/12.jpg'); assert.equal(article.url, 'https://en.wikipedia.org/?curid=12'); assert.equal(calls.length, 2); assert.equal(calls[0].url.origin, 'https://en.wikipedia.org'); assert.equal(calls[0].url.searchParams.get('generator'), 'random'); assert.equal(calls[0].url.searchParams.get('grnlimit'), '10'); assert.equal(calls[0].url.searchParams.get('piprop'), 'thumbnail'); assert.equal(calls[0].init.redirect, 'error'); }); test('only HTTPS Wikimedia pictures are eligible and random lookup has a bounded retry count', async () => { const { fetcher, calls } = mockFetch(() => json({ query: { pages: [ page(20, { thumbnail: { source: 'https://evil.example/tracker.jpg' } }), page(21, { thumbnail: { source: 'http://upload.wikimedia.org/image.jpg' } }), ] } })); await assert.rejects(new WikipediaArticleSource(fetcher).random(), /three attempts/); assert.equal(calls.length, 3); }); test('random article fetch rejects unsafe response types, redirects, and oversized payloads without fallback', async () => { const failures = [ () => new Response('{}', { headers: { 'Content-Type': 'text/html' } }), () => new Response(null, { status: 302, headers: { Location: 'https://evil.example' } }), () => new Response(' '.repeat(512 * 1024 + 1), { headers: { 'Content-Type': 'application/json' } }), () => { throw new Error('offline'); }, ]; for (const fail of failures) { const { fetcher, calls } = mockFetch(() => fail()); await assert.rejects(new WikipediaArticleSource(fetcher).random()); assert.equal(calls.length, 3); } }); test('bad page records are skipped while later valid candidates are considered', async () => { const { fetcher } = mockFetch(() => json({ query: { pages: [ page(30, { pageid: Number.MAX_SAFE_INTEGER + 1 }), page(31, { extract: 'short' }), page(32), ] } })); const article = await new WikipediaArticleSource(fetcher).random(); assert.equal(article.pageId, 32); }); test('a previously selected Wikipedia page may be selected again', async () => { const { fetcher } = mockFetch(() => json({ query: { pages: [page(40)] } })); const source = new WikipediaArticleSource(fetcher); const first = await source.random(); const second = await source.random(); assert.equal(first.pageId, second.pageId); });