Consolidate MangaCircus app, scraper, and Compose baseline

This commit is contained in:
Maashkinoozhe committed 2026-10-07 23:28:20 +02:00
1 parent cfb2d79103
commit eef4ffa07c
103 files changed
+9316 -172

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { ScraperService, validateSettings } = require('../service');
function fixture(scanLibrary) {
const service = new ScraperService({ scanLibrary, adapters: [] });
service.state = { version: 4, paused: true, status: 'paused', queue: [], settings: validateSettings({}), currentIds: {} };
service.recentEvents = async () => [];
return service;
}
const inventory = title => ({ mangas: title ? [{ id: title, title, status: 'ongoing', autoUpdate: true, chapters: [], sources: [] }] : [] });
test('cold status returns controls immediately and simultaneous probes share one slow scan', { timeout: 1000 }, async () => {
let finish, scans = 0;
const service = fixture(() => { scans++; return new Promise(resolve => { finish = resolve; }); });
const states = await Promise.all(Array.from({ length: 8 }, () => service.publicState()));
assert.equal(scans, 1);
for (const state of states) {
assert.equal(state.paused, true);
assert.equal(state.settings.sourceRates.mangadex.minMs, 1000);
assert.deepEqual(state.library, []);
assert.equal(state.libraryStatus.ready, false);
assert.equal(state.libraryStatus.refreshing, true);
}
finish(inventory('Demo'));
await service.inventoryRefresh;
const ready = await service.publicState();
assert.equal(ready.library[0].title, 'Demo');
assert.equal(ready.libraryStatus.ready, true);
assert.equal(ready.libraryStatus.stale, false);
assert.equal(scans, 1);
});
test('status keeps previous summary but authoritative callers await scans and reject invalidated results', { timeout: 1000 }, async () => {
const completions = [];
const service = fixture(() => new Promise(resolve => completions.push(resolve)));
const first = service.libraryInventory(true);
await Promise.resolve();
completions[0](inventory('Old'));
await first;
service.invalidateInventory();
const fresh = service.libraryInventory(true);
await Promise.resolve();
let finished = false;
fresh.then(() => { finished = true; });
let state = await service.publicState();
assert.equal(state.library[0].title, 'Old');
assert.equal(state.libraryStatus.stale, true);
assert.equal(state.libraryStatus.refreshing, true);
assert.equal(finished, false);
// Publication during an in-flight scan requires another authoritative scan.
service.invalidateInventory();
const scan = service.inventoryRefresh;
completions[1](inventory('Obsolete'));
await scan;
await Promise.resolve();
assert.equal(completions.length, 3);
assert.equal(finished, false);
completions[2](inventory('Published'));
assert.equal((await fresh).mangas[0].title, 'Published');
state = await service.publicState();
assert.equal(state.library[0].title, 'Published');
assert.equal(state.libraryStatus.stale, false);
});
test('background inventory failures are visible without breaking controls or launching a poll storm', { timeout: 1000 }, async () => {
let scans = 0;
const service = fixture(async () => { scans++; throw new Error('Scan failed https://test.example/a?token=private'); });
await service.publicState();
await assert.rejects(service.inventoryRefresh, /Scan failed/);
for (let n = 0; n < 3; n++) {
const state = await service.publicState();
assert.equal(state.paused, true);
assert.match(state.libraryStatus.error, /Scan failed/);
assert.ok(!state.libraryStatus.error.includes('private'));
}
assert.equal(scans, 1);
await assert.rejects(service.libraryInventory(true), /Scan failed/);
assert.equal(scans, 2);
});
test('dashboard polling reuses a recent snapshot while decisions still force a fresh scan', async () => {
let scans = 0;
const service = fixture(async () => { scans++; return inventory('Demo'); });
await service.libraryInventory(true);
service.inventoryCachedAt = Date.now() - 30000;
for (let n = 0; n < 5; n++) assert.equal((await service.publicState()).library[0].title, 'Demo');
assert.equal(scans, 1);
await service.libraryInventory(true);
assert.equal(scans, 2);
});