web: smoke-test the built standalone app against a mock graphql cms
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
// HTTP smoke tests against the built standalone app (see global-setup.ts).
|
||||
// These cover the surfaces most likely to break on a Next.js upgrade:
|
||||
// prerendered and request-rendered routes, 404s, the events API, the image
|
||||
// optimizer, the fetch-cache + tag revalidation round trip, and draft-mode
|
||||
// preview.
|
||||
|
||||
import { beforeAll, describe, expect, inject, test } from "vitest";
|
||||
|
||||
import { SLUGS } from "./fixtures";
|
||||
|
||||
const app = (path: string) => `${inject("appUrl")}${path}`;
|
||||
const mock = (path: string) => `${inject("mockUrl")}${path}`;
|
||||
|
||||
async function getPage(path: string): Promise<{ status: number; html: string }> {
|
||||
const res = await fetch(app(path));
|
||||
return { status: res.status, html: await res.text() };
|
||||
}
|
||||
|
||||
async function setNewsVersion(version: "v1" | "v2"): Promise<void> {
|
||||
const res = await fetch(mock("/__mock/state"), {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ newsHeadlineVersion: version }),
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
}
|
||||
|
||||
describe("pages", () => {
|
||||
test.each([
|
||||
["/", "Testkonsert"],
|
||||
["/aktuelt", "Testartikkel"],
|
||||
["/arrangementer", "Testkonsert"],
|
||||
[`/${SLUGS.genericUrl}`, "Praktisk info"],
|
||||
[`/foreninger/${SLUGS.association}`, "Testforeningen"],
|
||||
[`/lokaler/${SLUGS.venue}`, "Storsalen"],
|
||||
// not prerendered (empty generateStaticParams) — rendered on first request
|
||||
[`/aktuelt/${SLUGS.news}`, "Testartikkel"],
|
||||
[`/arrangementer/${SLUGS.event}`, "Testkonsert"],
|
||||
])("%s renders and contains %s", async (path, needle) => {
|
||||
const { status, html } = await getPage(path);
|
||||
expect(status).toBe(200);
|
||||
expect(html).toContain(needle);
|
||||
});
|
||||
|
||||
test.each([["/aktuelt/finnes-ikke"], ["/tull/og-toys"]])(
|
||||
"%s is a 404",
|
||||
async (path) => {
|
||||
const { status, html } = await getPage(path);
|
||||
expect(status).toBe(404);
|
||||
expect(html).toContain("404");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("events api", () => {
|
||||
test("rejects a missing view parameter", async () => {
|
||||
const res = await fetch(app("/api/v1/events"));
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
test("returns compact events", async () => {
|
||||
const res = await fetch(app("/api/v1/events?view=compact-app"));
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as {
|
||||
events: Array<{
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
nextOccurrence: { start: string } | null;
|
||||
}>;
|
||||
total: number;
|
||||
};
|
||||
expect(body.total).toBe(1);
|
||||
expect(body.events[0].slug).toBe(SLUGS.event);
|
||||
expect(body.events[0].nextOccurrence?.start).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("image optimizer", () => {
|
||||
test("optimizes a CMS-hosted image", async () => {
|
||||
const src = encodeURIComponent(mock("/media/pig.png"));
|
||||
const res = await fetch(app(`/_next/image?url=${src}&w=640&q=75`));
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("content-type")).toMatch(/^image\//);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetch cache and tag revalidation", () => {
|
||||
const path = `/aktuelt/${SLUGS.news}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setNewsVersion("v1");
|
||||
});
|
||||
|
||||
test("serves cached content until the webhook fires, then updates", async () => {
|
||||
// prime the cache (the route is request-rendered, then cached)
|
||||
const first = await getPage(path);
|
||||
expect(first.status).toBe(200);
|
||||
expect(first.html).toContain("Testartikkel (v1)");
|
||||
|
||||
// the CMS now has v2, but the app must keep serving cached v1
|
||||
await setNewsVersion("v2");
|
||||
const cached = await getPage(path);
|
||||
expect(cached.html).toContain("Testartikkel (v1)");
|
||||
|
||||
// webhook auth: missing/wrong secret is rejected
|
||||
const noSecret = await fetch(app("/api/revalidate"), { method: "POST" });
|
||||
expect(noSecret.status).toBe(401);
|
||||
const wrongSecret = await fetch(app("/api/revalidate"), {
|
||||
method: "POST",
|
||||
headers: { "x-revalidate-secret": "wrong" },
|
||||
});
|
||||
expect(wrongSecret.status).toBe(401);
|
||||
|
||||
// correct secret purges the cms cache tag
|
||||
const revalidated = await fetch(app("/api/revalidate"), {
|
||||
method: "POST",
|
||||
headers: { "x-revalidate-secret": "smoke-secret" },
|
||||
});
|
||||
expect(revalidated.status).toBe(200);
|
||||
expect(await revalidated.json()).toMatchObject({ revalidated: true });
|
||||
|
||||
// the next render must see v2 (allow a few retries for the refresh)
|
||||
const deadline = Date.now() + 5_000;
|
||||
let html = "";
|
||||
while (Date.now() < deadline) {
|
||||
({ html } = await getPage(path));
|
||||
if (html.includes("Testartikkel (v2)")) break;
|
||||
await new Promise((r) => setTimeout(r, 250));
|
||||
}
|
||||
expect(html).toContain("Testartikkel (v2)");
|
||||
});
|
||||
|
||||
test("prerendered pages pick up the change too", async () => {
|
||||
const { html } = await getPage("/");
|
||||
expect(html).toContain("Testartikkel (v2)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("preview", () => {
|
||||
test("draft mode round trip renders the preview page", async () => {
|
||||
const enable = await fetch(
|
||||
app("/api/preview?token=smoke-token&content_type=news.NewsPage"),
|
||||
{ redirect: "manual" }
|
||||
);
|
||||
expect([302, 307]).toContain(enable.status);
|
||||
expect(enable.headers.get("location")).toContain("/preview/render");
|
||||
|
||||
const setCookies = enable.headers.getSetCookie();
|
||||
expect(setCookies.length).toBeGreaterThan(0);
|
||||
const cookieHeader = setCookies
|
||||
.map((c) => c.split(";")[0])
|
||||
.join("; ");
|
||||
|
||||
const render = await fetch(app("/preview/render"), {
|
||||
headers: { cookie: cookieHeader },
|
||||
});
|
||||
expect(render.status).toBe(200);
|
||||
expect(await render.text()).toContain("Testartikkel");
|
||||
});
|
||||
|
||||
test("without the cookies the preview session is expired", async () => {
|
||||
const render = await fetch(app("/preview/render"));
|
||||
expect(render.status).toBe(200);
|
||||
expect(await render.text()).toContain("Preview session expired");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user