Files
mangabaka-seeder/test/pipeline.test.ts
T
Lucas WintherandClaude Opus 5.5 187e85fcaa feat(core): Tapas source (comics and novels), registered
Series by slug, id or episode link; portrait original and banner; age-checked
mature series resolve with a warning and no current covers; Wayback history
keeps only this series' captures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:56:03 +02:00

95 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, test } from "bun:test";
import { sha256Hex } from "../src/core/images";
import { LinkError } from "../src/core/links";
import { runCovers, type Stage } from "../src/core/pipeline";
import { FakeTransport, bytes, json, text } from "./helpers/fake-transport";
import { fixtureBytes, fixtureJson, fixtureText } from "./helpers/fixtures";
const API = "https://api.mangabaka.org";
const P = "https://swebtoon-phinf.pstatic.net";
const POSTER = `${P}/20250204_46/17386458444547o1b2_JPEG/95.jpg`;
const FOREGROUND = `${P}/20230324_64/1679612177711Smf0u_PNG/6TowerOfGod_landingpage_desktop_fg.png`;
/** Tower of God end to end on captured fixtures. Images other than three captured ones 404 (and are dropped). */
async function towerOfGod() {
const [p2015, p2023] = [await fixtureText("wayback/tower-of-god-2015.html"), await fixtureText("wayback/tower-of-god-2023.html")];
return new FakeTransport()
.on(`${API}/v1/series/9999`, async () => json(await fixtureJson("mangabaka/series-9999.json")))
.on(`${API}/v1/series/9999/links?limit=100`, async () => json(await fixtureJson("mangabaka/links-9999.json")))
.on(/\/v1\/series\/9999\/images\?.*page=2/, async () => json(await fixtureJson("mangabaka/images-9999-page2.json")))
.on(`${API}/v1/series/9999/images?limit=50`, async () => json(await fixtureJson("mangabaka/images-9999-page1.json")))
.on("https://www.webtoons.com/-/-/-/list?title_no=95", async () => text(await fixtureText("webtoons/tower-of-god.html")))
.on((u) => u.startsWith("https://web.archive.org/cdx/") && u.includes("www.webtoons.com"),
async () => text(await fixtureText("wayback/tower-of-god-cdx-www.txt")))
.on((u) => u.startsWith("https://web.archive.org/cdx/"), async () => text(await fixtureText("wayback/tower-of-god-cdx-m.txt")))
.on((u) => /\/web\/20(1\d)\d+id_\//.test(u) && !/\.(jpe?g|png)$/i.test(u), () => text(p2015))
.on((u) => /\/web\/20(2\d)\d+id_\//.test(u) && !/\.(jpe?g|png)$/i.test(u), () => text(p2023))
.on(POSTER, async () => bytes(await fixtureBytes("images/poster-95.jpg")))
.on(FOREGROUND, async () => bytes(await fixtureBytes("images/foreground-95.png"), "image/png"))
// The captured 2023-01 snapshot still shows the 2021 landing art; serve the captured 720×1230 landing image for it.
.on((u) => u.endsWith("1Tower-of-God-Mobile-Landing-Page.jpg"), async () => bytes(await fixtureBytes("images/landing-2023.jpg")))
.on(() => true, () => text("not found", 404));
}
describe("runCovers on Tower of God", () => {
test("current + yearly history: checked, deduplicated, compared, default suggested", async () => {
const fake = await towerOfGod();
const stages: Stage[] = [];
const r = await runCovers(
{ mangabakaLink: "https://mangabaka.org/manhwa/9999/Tower-of-God", sourceLink: "https://www.webtoons.com/en/fantasy/tower-of-god/list?title_no=95", history: 12 },
{ transport: fake },
{ onStage: (s) => stages.push(s) },
);
expect(stages).toEqual(["series", "source", "history", "inspect", "compare"]);
expect(r.seriesId).toBe(9999);
expect(r.mbSeries.title).toBe("Tower of God");
expect(r.series.language).toBe("en");
expect(r.linkMatches).toBe(true);
const all = [...r.fresh, ...r.present.map((p) => p.cover)];
const poster = all.find((c) => c.candidate.url === POSTER)!;
expect(poster).toMatchObject({ live: true, shape: "portrait", type: "volume", candidate: { current: true, role: "poster" } });
expect(r.suggestedDefault?.sha256).toBe(await sha256Hex(await fixtureBytes("images/poster-95.jpg")));
expect(all.find((c) => c.candidate.url.endsWith("1Tower-of-God-Mobile-Landing-Page.jpg"))).toMatchObject({ shape: "tall", candidate: { current: false } });
expect(all.find((c) => c.candidate.url === FOREGROUND)!.candidate.current).toBe(true);
expect(new Set(all.map((c) => c.sha256)).size).toBe(all.length);
expect(r.warnings.some((w) => w.startsWith("Could not download"))).toBe(true);
expect(fake.calls.some((c) => c.init.headers?.["x-api-key"])).toBe(false);
});
test("history off: Wayback is never asked", async () => {
const fake = await towerOfGod();
await runCovers({ mangabakaLink: "9999", sourceLink: "https://www.webtoons.com/-/-/-/list?title_no=95", history: null }, { transport: fake });
expect(fake.urls().some((u) => u.includes("web.archive.org"))).toBe(false);
});
test("'skip the rest of the history' keeps going with what was found", async () => {
const fake = await towerOfGod();
const stop = new AbortController();
const r = await runCovers(
{ mangabakaLink: "9999", sourceLink: "https://www.webtoons.com/-/-/-/list?title_no=95", history: 1 },
{ transport: fake },
{ stopHistory: stop.signal, onProgress: (stage) => stage === "history" && stop.abort() },
);
const snapshots = fake.urls().filter((u) => /id_\/http:\/\/(www|m)\.webtoons\.com/.test(u));
expect(snapshots).toHaveLength(1);
expect([...r.fresh, ...r.present.map((p) => p.cover)].some((c) => c.candidate.url === POSTER)).toBe(true);
});
test("warns when MangaBaka has no link to the Webtoons series", async () => {
const fake = await towerOfGod();
const r = await runCovers({ mangabakaLink: "9999", sourceLink: "https://www.webtoons.com/-/-/-/list?title_no=95", history: null }, {
transport: { fetch: (u, i) => (u.includes("/links") ? Promise.resolve(json({ status: 200, data: [], pagination: { next: null } })) : fake.fetch(u, i)) },
});
expect(r.linkMatches).toBe(false);
expect(r.warnings[0]).toMatch(/has no link to this WEBTOON series/);
});
});
test("unsupported source link is a LinkError naming the supported sources", async () => {
await expect(runCovers({ mangabakaLink: "9999", sourceLink: "https://www.lezhin.com/en/comic/x", history: null }, { transport: new FakeTransport() }))
.rejects.toThrow(/Supported: WEBTOON, Tapas/);
await expect(runCovers({ mangabakaLink: "nope", sourceLink: "https://tapas.io/series/x", history: null }, { transport: new FakeTransport() }))
.rejects.toBeInstanceOf(LinkError);
});