import { test, after } from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { execFileSync } from 'node:child_process'; import Fastify from 'fastify'; import JSZip from 'jszip'; import { authRoutes } from './auth.ts'; import { moderationRoutes } from './moderation.ts'; import { ebookRoutes, archiveId, parseMeta, metaText } from './ebooks.ts'; import { pool } from './db.ts'; after(() => pool.end()); test('e-book details and archive.org links', () => { assert.deepEqual(parseMeta(metaText({ title: 'کلیات اقبال', source: 'ریختہ نہیں', licence: 'عوامی ملکیت' })), { title: 'کلیات اقبال', source: 'ریختہ نہیں', licence: 'عوامی ملکیت', note: '' }); assert.match(parseMeta('ماخذ: کچھ') as string, /عنوان/); assert.equal(archiveId('https://archive.org/details/BaangEDara/page/n5'), 'BaangEDara'); assert.equal(archiveId('kulliyat-e-iqbal_202001'), 'kulliyat-e-iqbal_202001'); assert.equal(archiveId('https://example.com/x'), null); }); test('e-books: upload permission, file kinds, pipeline, published list and files, divan-data record', async () => { const data = await mkdtemp(join(tmpdir(), 'divan-data-')), files = await mkdtemp(join(tmpdir(), 'divan-files-')); Object.assign(process.env, { DIVAN_DATA_DIR: data, DIVAN_FILES_DIR: files }); const git = (...a: string[]) => execFileSync('git', ['-C', data, ...a], { encoding: 'utf8' }); git('init', '-q'); git('-c', 'user.name=t', '-c', 'user.email=t@t', 'commit', '-q', '--allow-empty', '-m', 'start'); const app = Fastify(); authRoutes(app); moderationRoutes(app); ebookRoutes(app); const run = Date.now(); const call = (method: string, url: string, body?: any, token?: string, type?: string) => app.inject({ method: method as any, url, payload: body, headers: { ...(token && { authorization: `Bearer ${token}` }), ...(type && { 'content-type': type }), 'x-client-ip': `eb-${run}` } }); const upload = (name: string, bytes: Buffer, token: string, poet = 238) => call('POST', `/api/mod/ebooks/upload?poet=${poet}&name=${encodeURIComponent(name)}`, bytes, token, 'application/octet-stream'); const person = async (name: string, role: string) => { const s = (await call('POST', '/api/auth/signup', { email: `${name}-${run}@divan.test`, password: 'pass-word-1' })).json(); await pool.query('UPDATE users SET role = $2 WHERE id = $1', [s.user.id, role]); return { ...s, id: s.user.id }; }; const admin = await person('admin', 'admin'), l1 = await person('l1', 'mod-l1'), l2 = await person('l2', 'mod-l2'); const pdf = Buffer.from('%PDF-1.4\n% آزمائشی کتاب ' + run + '\n%%EOF\n'); try { assert.equal((await upload('a.pdf', pdf, l2.token)).statusCode, 403, 'needs the ebooks permission'); for (const m of [l2, l1]) await pool.query(`INSERT INTO grants (user_id, scope, scope_id, content, actions) VALUES ($1, 'poet', 238, '{ebooks}', '{create}')`, [m.id]); assert.equal((await upload('a.pdf', pdf, l2.token, 266)).statusCode, 403, 'only for the granted poet'); // kinds: PDF, EPUB (by its mimetype entry), .docx made into text; anything else refused const up = (await upload('کتاب.pdf', pdf, l2.token)).json(); assert.equal(up.kind, 'pdf'); assert.match(up.file, /^[0-9a-f]{64}\.pdf$/); assert.equal((await upload('again.pdf', pdf, l2.token)).json().file, up.file, 'same file, same name (kept once)'); const epub = new JSZip(); epub.file('mimetype', 'application/epub+zip', { compression: 'STORE' }); epub.file('OEBPS/a.xhtml', '
ا
'); assert.equal((await upload('b.epub', await epub.generateAsync({ type: 'nodebuffer' }), l2.token)).json().kind, 'epub'); const docx = new JSZip(); docx.file('[Content_Types].xml', '