Files
runners-calendar/backend/test/app.test.ts
Vaka.pro 0153f223f2
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
feat: add race cover image extraction
2026-04-27 22:56:41 +03:00

170 lines
5.0 KiB
TypeScript

import assert from "node:assert/strict";
import { test } from "node:test";
import request from "supertest";
import { createApp } from "../src/app";
import { extractRaceCoverImageFromHtml } from "../src/raceCoverImage";
const app = createApp();
test("GET /api/health returns ok", async () => {
const res = await request(app).get("/api/health").expect(200);
assert.equal(res.body.status, "ok");
assert.equal(typeof res.body.version, "string");
assert.ok(res.body.version.length > 0);
});
test("GET /api/meta returns version for UI footer", async () => {
const res = await request(app).get("/api/meta").expect(200);
assert.equal(typeof res.body.version, "string");
assert.ok(res.body.version.length > 0);
});
test("GET /api/ready succeeds with mock database", async () => {
const res = await request(app).get("/api/ready").expect(200);
assert.equal(res.body.status, "ready");
assert.equal(res.body.db, "connected");
});
test("GET /api/races rejects invalid year", async () => {
const res = await request(app).get("/api/races?year=bad").expect(400);
assert.equal(res.body.error, "validation_error");
assert.ok(Array.isArray(res.body.details));
});
test("GET /api/races rejects month out of range", async () => {
const res = await request(app).get("/api/races?month=13").expect(400);
assert.equal(res.body.error, "validation_error");
});
test("GET /api/races accepts year and month", async () => {
const res = await request(app).get("/api/races?year=2026&month=5").expect(200);
assert.ok(Array.isArray(res.body));
});
test("GET /api/races/:id returns not_found", async () => {
const res = await request(app).get("/api/races/does-not-exist").expect(404);
assert.equal(res.body.error, "not_found");
assert.ok(Array.isArray(res.body.details));
});
test("extractRaceCoverImageFromHtml prefers runc.run intro image", () => {
const html = `
<meta property="og:image" content="https://example.com/og.jpg">
<div class="run-intro__image">
<div class="run-intro__image-left-shadow"></div>
<img src="/uploads/race_landing_header_backgrounds/header.jpg" alt="">
<div class="run-intro__image-right-shadow"></div>
</div>
`;
assert.equal(
extractRaceCoverImageFromHtml(html, "https://aprilrun5km.runc.run/"),
"https://aprilrun5km.runc.run/uploads/race_landing_header_backgrounds/header.jpg",
);
});
test("extractRaceCoverImageFromHtml reads Open Graph and Twitter images", () => {
assert.equal(
extractRaceCoverImageFromHtml(
'<meta property="og:image" content="/cover.png">',
"https://example.com/race",
),
"https://example.com/cover.png",
);
assert.equal(
extractRaceCoverImageFromHtml(
'<meta name="twitter:image" content="https://cdn.example.com/twitter.jpg">',
"https://example.com/race",
),
"https://cdn.example.com/twitter.jpg",
);
});
test("POST /api/races stores manual coverImageUrl", async () => {
const coverImageUrl = "https://example.com/manual.jpg";
const res = await request(app)
.post("/api/races")
.send({
id: "2026-06-01-manual-cover",
date: "2026-06-01",
title: "Manual Cover",
distanceKm: 10,
officialUrl: "https://example.com/race",
coverImageUrl,
})
.expect(201);
assert.equal(res.body.coverImageUrl, coverImageUrl);
});
test("POST /api/races auto extracts coverImageUrl from officialUrl", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () =>
new Response('<meta property="og:image" content="/auto.jpg">', {
status: 200,
headers: { "content-type": "text/html" },
});
try {
const res = await request(app)
.post("/api/races")
.send({
id: "2026-06-02-auto-cover",
date: "2026-06-02",
title: "Auto Cover",
distanceKm: 21.1,
officialUrl: "https://example.com/race",
})
.expect(201);
assert.equal(res.body.coverImageUrl, "https://example.com/auto.jpg");
} finally {
globalThis.fetch = originalFetch;
}
});
test("POST /api/races succeeds when cover extraction fails", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = async () => {
throw new Error("network down");
};
try {
const res = await request(app)
.post("/api/races")
.send({
id: "2026-06-03-cover-fail",
date: "2026-06-03",
title: "Cover Fail",
distanceKm: 5,
officialUrl: "https://example.com/race",
})
.expect(201);
assert.equal(res.body.coverImageUrl, null);
} finally {
globalThis.fetch = originalFetch;
}
});
test("PATCH /api/races/:id updates coverImageUrl explicitly", async () => {
const id = "2026-06-04-patch-cover";
await request(app)
.post("/api/races")
.send({
id,
date: "2026-06-04",
title: "Patch Cover",
distanceKm: 10,
})
.expect(201);
const coverImageUrl = "https://example.com/patched.jpg";
const res = await request(app)
.patch(`/api/races/${id}`)
.send({ coverImageUrl })
.expect(200);
assert.equal(res.body.coverImageUrl, coverImageUrl);
});