Files
mangabaka-seeder/test/mangabaka-client.test.ts
T

99 lines
5.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { MangaBakaClient, MangaBakaError, coverFormUrl } from "../src/core/mangabaka/client";
import { toExisting, type SubmissionImage } from "../src/core/mangabaka/types";
import { FakeTransport, json } from "./helpers/fake-transport";
import { fixtureJson } from "./helpers/fixtures";
const API = "https://api.mangabaka.org";
function publicFake() {
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")));
}
describe("public reads (captured fixtures)", () => {
test("series, links, and every page of images", async () => {
const fake = publicFake();
const mb = new MangaBakaClient(fake);
expect((await mb.getSeries(9999)).title).toBe("Tower of God");
expect((await mb.getLinks(9999)).map((l) => l.url)).toContain("https://www.webtoons.com/-/-/-/list?title_no=95");
const images = await mb.getImages(9999);
expect(images).toHaveLength(31);
expect(fake.calls.some((c) => c.init.headers?.["x-api-key"])).toBe(false);
expect(toExisting(images[0]!)).toEqual({
id: images[0]!.id, sha256: images[0]!.hashes!.sha256 ?? null, language: images[0]!.language, type: images[0]!.type,
});
});
test("a next link to another origin is not followed", async () => {
const fake = new FakeTransport().on(`${API}/v1/series/1/images?limit=50`, () =>
json({ status: 200, data: [], pagination: { next: "https://evil.example/page2" } }));
expect(await new MangaBakaClient(fake).getImages(1)).toEqual([]);
expect(fake.calls).toHaveLength(1);
});
test("404 is a MangaBakaError", async () => {
const fake = new FakeTransport().on(() => true, () => json({ status: 404, message: "Not found" }, 404));
const err = await new MangaBakaClient(fake).getSeries(1).catch((e) => e);
expect(err).toBeInstanceOf(MangaBakaError);
expect(err.status).toBe(404);
});
});
// Not capturable without a token: shapes built from the OpenAPI spec (spec §2.3). See docs/TODO.md.
const existing: SubmissionImage = {
id: 7193, series_id: 9999, work_id: null, url: "https://example.org/old.jpg", index: "0.1", index_numeric: 0.1,
language: "ko", type: "volume", note: "Season Cover 3", content_rating: "safe", is_default: true,
};
const snapshotBody = { status: 200, data: { data: { images: [existing] }, version: 1760000000 } };
const previewBody = { status: 200, data: { has_changes: true, changes: [{ field: "images", type: "added", identity: "https://x/new.jpg" }] } };
describe("submissions (token)", () => {
const SUB = `${API}/v0/my/submissions/series-images/9999`;
test("snapshot, preview and save send the token and the right bodies", async () => {
const fake = new FakeTransport()
.on((u, i) => u === SUB && (i.method ?? "GET") === "GET", () => json(snapshotBody))
.on(`${SUB}/preview`, () => json(previewBody))
.on((u, i) => u === SUB && i.method === "POST", () =>
json({ status: 200, data: { submission_id: 5, status: "pending", changes: [] } }));
const mb = new MangaBakaClient(fake, "mb-test-token");
const snapshot = await mb.getImageSnapshot(9999);
expect(snapshot).toEqual({ version: 1760000000, images: [existing] });
expect((await mb.previewImages(9999, snapshot.version, snapshot.images)).has_changes).toBe(true);
const saved = await mb.saveImagesForReview(9999, snapshot.version, snapshot.images, "Add WEBTOON covers");
expect(saved.submission_id).toBe(5);
expect(fake.calls.every((c) => c.init.headers?.["x-api-key"] === "mb-test-token")).toBe(true);
expect(JSON.parse(fake.calls[1]!.init.body as string)).toEqual({ version: 1760000000, data: { images: [existing] } });
expect(JSON.parse(fake.calls[2]!.init.body as string)).toEqual({
version: 1760000000, user_note: "Add WEBTOON covers", save_mode: "review", data: { images: [existing] },
});
});
test("no token: refuses before sending anything", async () => {
const fake = new FakeTransport();
await expect(new MangaBakaClient(fake).getImageSnapshot(9999)).rejects.toMatchObject({ status: 401 });
expect(fake.calls).toHaveLength(0);
});
test.each([
[403, /25 approved submissions/],
[409, /changed since/],
[401, /token/],
])("HTTP %i explains itself", async (status, message) => {
const fake = new FakeTransport().on(() => true, () => json({ status, message: "x" }, status));
const err = await new MangaBakaClient(fake, "mb-t").previewImages(9999, 1, []).catch((e) => e);
expect(err).toBeInstanceOf(MangaBakaError);
expect(err.status).toBe(status);
expect(err.message).toMatch(message);
});
});
test("coverFormUrl", () => {
expect(coverFormUrl(9999)).toBe("https://mangabaka.org/data/submissions/series/9999/images");
});