forked from admin/runners-calendar
feat: complete race history sprint
This commit is contained in:
@@ -4,6 +4,7 @@ import path from "node:path";
|
||||
import { after, test } from "node:test";
|
||||
import request from "supertest";
|
||||
import { buildHelmetOptions, createApp } from "../src/app";
|
||||
import { backfillRaceCovers } from "../src/backfillRaceCovers";
|
||||
import {
|
||||
cleanupExpiredAuthRows,
|
||||
createResetToken,
|
||||
@@ -161,6 +162,29 @@ test("GET /api/races accepts year and month", async () => {
|
||||
assert.ok(Array.isArray(res.body));
|
||||
});
|
||||
|
||||
test("GET /api/races filters by status, date and distance", async () => {
|
||||
const { agent, csrfToken } = await authAgent();
|
||||
await agent.post("/api/races").set("X-CSRF-Token", csrfToken).send({
|
||||
slug: "2026-05-01-filter-planned", date: "2026-05-01", title: "Planned 5", distanceKm: 5, status: "planned",
|
||||
}).expect(201);
|
||||
await agent.post("/api/races").set("X-CSRF-Token", csrfToken).send({
|
||||
slug: "2026-06-01-filter-completed", date: "2026-06-01", title: "Completed 10", distanceKm: 10, status: "completed",
|
||||
}).expect(201);
|
||||
await agent.post("/api/races").set("X-CSRF-Token", csrfToken).send({
|
||||
slug: "2026-07-01-filter-completed", date: "2026-07-01", title: "Completed 21", distanceKm: 21.1, status: "completed",
|
||||
}).expect(201);
|
||||
|
||||
const res = await agent.get("/api/races?status=completed&dateFrom=2026-06-01&dateTo=2026-06-30&distanceMax=10").expect(200);
|
||||
assert.deepEqual(res.body.map((race: { title: string }) => race.title), ["Completed 10"]);
|
||||
});
|
||||
|
||||
test("GET /api/races rejects inverted filter ranges", async () => {
|
||||
const { agent } = await authAgent();
|
||||
const res = await agent.get("/api/races?dateFrom=2026-07-01&dateTo=2026-06-01").expect(400);
|
||||
assert.equal(res.body.error, "validation_error");
|
||||
assert.ok(res.body.details.some((detail: string) => detail.includes("dateFrom")));
|
||||
});
|
||||
|
||||
test("GET /api/races/:id rejects a non-UUID id", async () => {
|
||||
const { agent } = await authAgent();
|
||||
const res = await agent.get("/api/races/does-not-exist").expect(400);
|
||||
@@ -460,6 +484,39 @@ test("POST /api/races stores manual coverImageUrl", async () => {
|
||||
assert.equal(res.body.coverImageUrl, coverImageUrl);
|
||||
});
|
||||
|
||||
test("PATCH /api/races refreshes an automatic cover but preserves a manual cover", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let imageName = "first.jpg";
|
||||
globalThis.fetch = async () => new Response(`<meta property="og:image" content="/${imageName}">`, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/html" },
|
||||
});
|
||||
|
||||
try {
|
||||
const { agent, csrfToken } = await authAgent();
|
||||
const created = await agent.post("/api/races").set("X-CSRF-Token", csrfToken).send({
|
||||
slug: "2026-06-06-auto-refresh", date: "2026-06-06", title: "Auto Refresh", distanceKm: 10, officialUrl: "https://example.com/first",
|
||||
}).expect(201);
|
||||
imageName = "second.jpg";
|
||||
const refreshed = await agent.patch(`/api/races/${created.body.id}`).set("X-CSRF-Token", csrfToken).send({
|
||||
officialUrl: "https://example.com/second",
|
||||
}).expect(200);
|
||||
assert.equal(refreshed.body.coverImageUrl, "https://example.com/second.jpg");
|
||||
|
||||
const manual = await agent.patch(`/api/races/${created.body.id}`).set("X-CSRF-Token", csrfToken).send({
|
||||
officialUrl: "https://example.com/third", coverImageUrl: "https://example.com/manual.jpg",
|
||||
}).expect(200);
|
||||
imageName = "fourth.jpg";
|
||||
const preserved = await agent.patch(`/api/races/${created.body.id}`).set("X-CSRF-Token", csrfToken).send({
|
||||
officialUrl: "https://example.com/fourth",
|
||||
}).expect(200);
|
||||
assert.equal(manual.body.coverImageUrl, "https://example.com/manual.jpg");
|
||||
assert.equal(preserved.body.coverImageUrl, "https://example.com/manual.jpg");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("POST /api/races rejects unknown and malformed fields", async () => {
|
||||
const { agent, csrfToken } = await authAgent();
|
||||
const res = await agent
|
||||
@@ -526,6 +583,58 @@ test("POST /api/races auto extracts coverImageUrl from officialUrl", async () =>
|
||||
}
|
||||
});
|
||||
|
||||
test("PATCH /api/races auto extracts a missing cover when officialUrl changes", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => new Response("", { status: 404 });
|
||||
|
||||
try {
|
||||
const { agent, csrfToken } = await authAgent();
|
||||
const created = await agent
|
||||
.post("/api/races")
|
||||
.set("X-CSRF-Token", csrfToken)
|
||||
.send({ slug: "2026-06-04-patch-cover", date: "2026-06-04", title: "Patch Cover", distanceKm: 10 })
|
||||
.expect(201);
|
||||
|
||||
globalThis.fetch = async () => new Response('<meta property="og:image" content="/patched.jpg">', {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/html" },
|
||||
});
|
||||
const updated = await agent
|
||||
.patch(`/api/races/${created.body.id}`)
|
||||
.set("X-CSRF-Token", csrfToken)
|
||||
.send({ officialUrl: "https://example.com/patched" })
|
||||
.expect(200);
|
||||
|
||||
assert.equal(updated.body.coverImageUrl, "https://example.com/patched.jpg");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("cover backfill restores only races without a cover", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => new Response('<meta property="og:image" content="/backfill.jpg">', {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/html" },
|
||||
});
|
||||
|
||||
try {
|
||||
const { agent, csrfToken } = await authAgent();
|
||||
await agent.post("/api/races").set("X-CSRF-Token", csrfToken).send({
|
||||
slug: "2026-06-05-backfill", date: "2026-06-05", title: "Backfill", distanceKm: 10,
|
||||
}).expect(201);
|
||||
await pool.query("UPDATE races SET official_url = $1 WHERE slug = $2", ["https://example.com/backfill", "2026-06-05-backfill"]);
|
||||
|
||||
const stats = await backfillRaceCovers();
|
||||
assert.ok(stats.candidates >= 1);
|
||||
assert.ok(stats.restored >= 1);
|
||||
const result = await pool.query<{ cover_image_url: string }>("SELECT cover_image_url FROM races WHERE slug = $1", ["2026-06-05-backfill"]);
|
||||
assert.equal(result.rows[0]?.cover_image_url, "https://example.com/backfill.jpg");
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("POST /api/races succeeds when cover extraction fails", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () => {
|
||||
|
||||
Reference in New Issue
Block a user