diff --git a/web/src/app/api/v1/events/route.test.ts b/web/src/app/api/v1/events/route.test.ts new file mode 100644 index 0000000..f469f24 --- /dev/null +++ b/web/src/app/api/v1/events/route.test.ts @@ -0,0 +1,138 @@ +import { NextRequest } from "next/server"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const queryMock = vi.fn(); +vi.mock("@/app/client", () => ({ + getClient: () => ({ query: queryMock }), +})); + +import { GET } from "./route.ts"; + +const request = (path: string) => new NextRequest(`http://localhost${path}`); + +const occurrence = (id: string, start: string) => ({ + id, + start, + end: null, + venue: { + id: "v1", + slug: "storsalen", + title: "Storsalen", + preposition: "i", + url: "/lokaler/storsalen", + }, +}); + +const data = (futureEvents: unknown[]) => ({ + index: { id: "idx" }, + events: { futureEvents }, + eventCategories: [], + eventOrganizers: [], + venues: [], +}); + +// "now" is 2026-07-07 12:00 in Oslo +beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-07-07T10:00:00Z")); + vi.stubEnv("URL", "https://neuf.no"); +}); + +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllEnvs(); + vi.clearAllMocks(); +}); + +describe("GET /api/v1/events", () => { + it("rejects a missing or unknown view parameter", async () => { + for (const path of ["/api/v1/events", "/api/v1/events?view=full"]) { + const res = await GET(request(path)); + expect(res.status).toBe(400); + expect(await res.json()).toEqual({ + error: "must provide valid view parameter", + }); + } + expect(queryMock).not.toHaveBeenCalled(); + }); + + it("returns compact events with the earliest future occurrence", async () => { + queryMock.mockResolvedValue({ + data: data([ + { + id: "a", + slug: "konsert", + title: "Konsert", + subtitle: "Med kor", + occurrences: [ + occurrence("a-0", "2026-07-01T18:00:00Z"), // past, dropped + occurrence("a-1", "2026-07-12T18:00:00Z"), + occurrence("a-2", "2026-07-08T18:00:00Z"), + ], + }, + ]), + }); + + const res = await GET(request("/api/v1/events?view=compact-app")); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ + events: [ + { + id: "a", + slug: "konsert", + title: "Konsert", + subtitle: "Med kor", + nextOccurrence: occurrence("a-2", "2026-07-08T18:00:00Z"), + url: "https://neuf.no/arrangementer/konsert", + futureOccurrencesCount: 2, + }, + ], + total: 1, + }); + }); + + it("keeps events with no future occurrences, with a null nextOccurrence", async () => { + queryMock.mockResolvedValue({ + data: data([ + { + id: "b", + slug: "fortid", + title: "Fortid", + subtitle: "", + occurrences: [occurrence("b-0", "2026-07-01T18:00:00Z")], + }, + ]), + }); + + const res = await GET(request("/api/v1/events?view=compact-app")); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ + events: [ + { + id: "b", + slug: "fortid", + title: "Fortid", + subtitle: "", + nextOccurrence: null, + url: "https://neuf.no/arrangementer/fortid", + futureOccurrencesCount: 0, + }, + ], + total: 1, + }); + }); + + it("throws on a query error or missing data", async () => { + queryMock.mockResolvedValue({ error: { message: "cms nede" } }); + await expect(GET(request("/api/v1/events?view=compact-app"))).rejects.toThrow( + "cms nede" + ); + + queryMock.mockResolvedValue({ data: { index: { id: "idx" } } }); + await expect(GET(request("/api/v1/events?view=compact-app"))).rejects.toThrow( + "Failed to fetch events" + ); + }); +});