Files
MangaCircus/scraper/test/dashboard.test.js
T

72 lines
5.0 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs').promises;
const os = require('node:os');
const path = require('node:path');
const { ScraperService } = require('../service');
const adapter = { id: 'alpha', label: 'Alpha',
match(value) { return value.startsWith('https://alpha.test/') ? { id: '42', source: 'alpha', title: 'Test', url: value } : null; },
async discover() { return { title: 'Test', chapters: [{ id: 'one', number: '1' }] }; },
async run() { throw new Error('A no-change check must not download'); },
};
const resolve = value => { const info = adapter.match(value); return info ? { adapter, info } : null; };
const waitFor = async predicate => {
const deadline = Date.now() + 3000;
while (!predicate()) {
if (Date.now() > deadline) throw new Error('Timed out waiting for local discovery');
await new Promise(resolveWait => setTimeout(resolveWait, 25));
}
};
test('finished-job cleanup preserves unfinished jobs, legacy check summaries, history and library files', async t => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mc-scraper-dashboard-'));
const dataRoot = path.join(root, 'data'), libraryRoot = path.join(root, 'library');
await fs.mkdir(dataRoot); await fs.mkdir(path.join(libraryRoot, 'Test'), { recursive: true });
const chapterFile = path.join(libraryRoot, 'Test', 'keep.jpg');
await fs.writeFile(chapterFile, 'library-bytes');
const queue = [
{ id: 'done-a', kind: 'download', source: 'alpha', url: 'https://alpha.test/42', status: 'complete' },
{ id: 'checked', kind: 'discovery', source: 'alpha', url: 'https://alpha.test/42', status: 'complete', libraryMangaId: 'Test', checked: '2026-10-06T12:00:00Z' },
{ id: 'ready', kind: 'inspection', source: 'alpha', url: 'https://alpha.test/42', status: 'ready' },
{ id: 'failed', kind: 'download', source: 'alpha', url: 'https://alpha.test/42', status: 'quarantined' },
{ id: 'queued', kind: 'download', source: 'alpha', url: 'https://alpha.test/42', status: 'queued' },
{ id: 'done-b', kind: 'download', source: 'beta', url: 'https://beta.test/42', status: 'complete' },
];
await fs.writeFile(path.join(dataRoot, 'queue.json'), JSON.stringify({ paused: true, queue, history: [{ id: 'done-a', status: 'complete' }] }));
const service = await new ScraperService({ libraryRoot, dataRoot, adapterFor: resolve, adapters: [adapter, { id: 'beta', label: 'Beta' }] }).init();
t.after(async () => { await service.close(); await fs.rm(root, { recursive: true, force: true }); });
assert.deepEqual(service.state.queue.map(item => item.id), ['done-a', 'ready', 'failed', 'queued', 'done-b']);
assert.equal((await service.publicState()).updateChecks[0].lastCheckedAt, '2026-10-06T12:00:00Z');
assert.equal((await service.clearCompleted('alpha')).cleared, 1);
assert.deepEqual(service.state.queue.map(item => item.id), ['ready', 'failed', 'queued', 'done-b']);
assert.equal((await service.clearCompleted()).cleared, 1);
assert.equal((await service.clearCompleted()).cleared, 0);
assert.deepEqual(service.state.queue.map(item => item.id), ['ready', 'failed', 'queued']);
assert.deepEqual(service.state.queue.map(item => item.order), [0, 1, 2]);
assert.equal((await fs.readFile(chapterFile, 'utf8')), 'library-bytes');
assert.equal(service.state.history[0].id, 'done-a');
assert.equal(JSON.parse(await fs.readFile(path.join(dataRoot, 'queue.json'), 'utf8')).updateChecks['["Test","alpha"]'].lastCheckedAt, '2026-10-06T12:00:00Z');
});
test('a nightly no-change check updates the latest timestamp and retires its queue row', async t => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mc-scraper-check-summary-'));
const dataRoot = path.join(root, 'data'), libraryRoot = path.join(root, 'library');
const chapter = path.join(libraryRoot, 'Test', 'v01', 'c0001 [Alpha]');
await fs.mkdir(dataRoot); await fs.mkdir(chapter, { recursive: true });
await fs.writeFile(path.join(libraryRoot, 'Test', 'manga.json'), JSON.stringify({ title: 'Test', status: 'ongoing', sources: [{ name: 'Alpha', url: 'https://alpha.test/title/42' }] }));
await fs.writeFile(path.join(chapter, 'chapter.json'), JSON.stringify({ source: { name: 'Alpha', url: 'https://alpha.test/chapter/one' } }));
await fs.writeFile(path.join(chapter, '0001.png'), Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 0]));
const service = await new ScraperService({ libraryRoot, dataRoot, adapterFor: resolve, adapters: [adapter] }).init();
t.after(async () => { await service.close(); await fs.rm(root, { recursive: true, force: true }); });
await service.enqueueDiscoveryChecks('schedule');
await waitFor(() => service.state.updateChecks['["Test","alpha"]']?.lastCheckedAt);
assert.equal(service.state.queue.length, 0);
const summary = (await service.publicState()).updateChecks[0];
assert.equal(summary.lastResult, 'no-updates');
assert.equal(summary.lastNewContentAt, undefined);
assert.equal(summary.lastDownloadedAt, undefined);
assert.ok(service.state.history.some(item => item.status === 'checked'));
});