Files
mangabaka-seeder/test/kakaopage-source.test.ts
T
Lucas WintherandClaude Opus 5.5 5b2ad867b3 feat: KakaoPage source (웹툰 and 웹소설), registered; correct the Tapas cookie claim
KakaoPage is read from its data endpoint (current covers) and from archived
server-rendered pages under both addresses (history). The userscript never sent
Tapas cookies (anonymous: true): docs and the age-check warning now say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:20:29 +02:00

63 lines
4.6 KiB
TypeScript

import { expect, test } from "bun:test";
import { waybackHistory } from "../src/core/history/wayback";
import { allowedFetchHosts, allowedImageHosts, findSource } from "../src/core/sources/registry";
import { SourceError } from "../src/core/sources/source";
import { kakaopage } from "../src/core/sources/kakaopage";
import { FakeTransport, json, text } from "./helpers/fake-transport";
import { fixtureJson, fixtureText } from "./helpers/fixtures";
const API = "https://bff-page.kakao.com/api/gateway/api/v1/content/overview?series_id=";
const overview = (name: string) => async () => json(await fixtureJson(`kakaopage/${name}.json`));
test("registered; its hosts are allowlisted", () => {
expect(findSource(new URL("https://page.kakao.com/content/54845189"))?.id).toBe("kakaopage");
for (const host of ["page.kakao.com", "bff-page.kakao.com", "page-images.kakaoentcdn.com", "dn-img-page.kakao.com"]) expect(allowedFetchHosts().has(host)).toBe(true);
for (const host of ["page-images.kakaoentcdn.com", "dn-img-page.kakao.com"]) expect(allowedImageHosts().has(host)).toBe(true);
});
test("resolve reads the overview; currentCovers reuses it", async () => {
const fake = new FakeTransport().on(`${API}54845189`, overview("overview-54845189"));
const series = await kakaopage.resolve(new URL("https://page.kakao.com/home?seriesId=54845189"), { transport: fake });
expect(series).toEqual({
source: "kakaopage", externalId: "54845189", title: "악역의 엔딩은 죽음뿐", language: "ko",
canonicalUrl: "https://page.kakao.com/content/54845189",
archiveUrls: ["page.kakao.com/content/54845189", "page.kakao.com/home?seriesId=54845189"],
mature: false, flags: { novel: false },
});
const covers = await kakaopage.currentCovers(series, { transport: fake });
expect(fake.calls.length).toBe(1);
expect(covers.map((c) => [c.role, c.current])).toEqual([["poster", true], ["banner", true]]);
});
test("19+ is mature; a novel is flagged", async () => {
const fake = new FakeTransport().on(`${API}63659962`, overview("overview-63659962-19")).on(`${API}52654302`, overview("overview-52654302-novel"));
expect((await kakaopage.resolve(new URL("https://page.kakao.com/content/63659962"), { transport: fake })).mature).toBe(true);
expect((await kakaopage.resolve(new URL("https://page.kakao.com/content/52654302"), { transport: fake })).flags.novel).toBe(true);
});
test("an id KakaoPage doesn't know, and an HTTP error", async () => {
const fake = new FakeTransport().on(`${API}53998542`, overview("overview-not-found")).on(`${API}1`, () => text("blocked", 403));
await expect(kakaopage.resolve(new URL("https://page.kakao.com/content/53998542"), { transport: fake })).rejects.toThrow(/상품 정보를 찾을 수 없습니다/);
await expect(kakaopage.resolve(new URL("https://page.kakao.com/content/1"), { transport: fake })).rejects.toBeInstanceOf(SourceError);
});
test("Wayback history through both addresses keeps only this id", async () => {
const fake = new FakeTransport().on(`${API}54845189`, overview("overview-54845189"))
.on((u) => u.startsWith("https://web.archive.org/cdx/") && u.includes("home"), async () => text(await fixtureText("wayback/kakaopage-54845189-cdx-home.txt")))
.on((u) => u.startsWith("https://web.archive.org/cdx/"), async () => text(await fixtureText("wayback/kakaopage-54845189-cdx-content.txt")))
.on((u) => /\/web\/2020\d+id_\//.test(u), async () => text(await fixtureText("wayback/kakaopage-54845189-2020.html")))
.on((u) => /\/web\/202[12]\d+id_\//.test(u), async () => text(await fixtureText("wayback/kakaopage-54845189-2022.html")))
.on((u) => /\/web\/20\d+id_\//.test(u), async () => text(await fixtureText("wayback/kakaopage-54845189-2024.html")));
const series = await kakaopage.resolve(new URL("https://page.kakao.com/content/54845189"), { transport: fake });
const found = await waybackHistory(kakaopage, series, { every: 12, transport: fake, warn: () => {} });
const snapshots = fake.urls().filter((u) => u.includes("id_/"));
expect(snapshots.length).toBeGreaterThan(3);
expect(snapshots.every((u) => /seriesId=54845189(&|$)|content\/54845189(\/|$|\?)/.test(u))).toBe(true);
expect(new Set(found.map((c) => c.url))).toEqual(new Set([
"https://page-images.kakaoentcdn.com/download/resource?kid=dNtPVq/hyCvfq21v6/b4EGtZDhGNKvzdBjWXg290&filename=o1",
"https://page-images.kakaoentcdn.com/download/resource?kid=bx4cET/hyATqBNkj9/kUeJCNRNNpYDYRBtpK1Oh1&filename=o1",
"https://page-images.kakaoentcdn.com/download/resource?kid=RmobN/hzCtfKUqaD/6XgE5RjbBGMOn6thodamr0&filename=o1",
"https://page-images.kakaoentcdn.com/download/resource?kid=dBnFgi/hzXap8fqJ4/Kzjixkeeek6kH1eOyRb490&filename=o1",
]));
});