'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')); });