web: smoke-test the built standalone app against a mock graphql cms
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
// A tiny stand-in for the Wagtail CMS, reachable over HTTP so both
|
||||
// `next build` and the standalone server (separate child processes) can
|
||||
// query it. Serves the canned GraphQL responses from fixtures.ts, a 1x1 PNG
|
||||
// for image URLs, and a state endpoint the revalidation test uses to
|
||||
// "publish a change" in the CMS.
|
||||
|
||||
import http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
|
||||
import { makeFixtures, type Fixtures, type MockState } from "./fixtures";
|
||||
|
||||
// 1x1 transparent PNG
|
||||
const TINY_PNG = Buffer.from(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
|
||||
"base64"
|
||||
);
|
||||
|
||||
export type MockCms = {
|
||||
url: string;
|
||||
state: MockState;
|
||||
graphqlRequests: number;
|
||||
close(): Promise<void>;
|
||||
};
|
||||
|
||||
function readBody(req: http.IncomingMessage): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
req.on("data", (c) => chunks.push(c));
|
||||
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
||||
req.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
export async function startMockCms(): Promise<MockCms> {
|
||||
const state: MockState = { newsHeadlineVersion: "v1" };
|
||||
let fixtures: Fixtures | null = null; // built once we know our own URL
|
||||
|
||||
const mock: MockCms = {
|
||||
url: "",
|
||||
state,
|
||||
graphqlRequests: 0,
|
||||
close: () =>
|
||||
new Promise((resolve, reject) =>
|
||||
server.close((err) => (err ? reject(err) : resolve()))
|
||||
),
|
||||
};
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
try {
|
||||
if (req.method === "GET" && req.url?.startsWith("/media/")) {
|
||||
res.writeHead(200, {
|
||||
"content-type": "image/png",
|
||||
"content-length": TINY_PNG.length,
|
||||
});
|
||||
res.end(TINY_PNG);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === "POST" && req.url === "/__mock/state") {
|
||||
Object.assign(state, JSON.parse(await readBody(req)));
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
res.end("{}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url?.startsWith("/api/graphql")) {
|
||||
mock.graphqlRequests += 1;
|
||||
// urql sends both POST (JSON body) and GET (URL params) requests
|
||||
let body: {
|
||||
query?: string;
|
||||
operationName?: string;
|
||||
variables?: Record<string, unknown>;
|
||||
};
|
||||
if (req.method === "GET") {
|
||||
const params = new URL(req.url, mock.url).searchParams;
|
||||
body = {
|
||||
query: params.get("query") ?? undefined,
|
||||
operationName: params.get("operationName") ?? undefined,
|
||||
variables: JSON.parse(params.get("variables") ?? "{}"),
|
||||
};
|
||||
} else {
|
||||
body = JSON.parse(await readBody(req));
|
||||
}
|
||||
const name =
|
||||
body.operationName ??
|
||||
/(?:query|mutation)\s+(\w+)/.exec(body.query ?? "")?.[1];
|
||||
const resolve = name ? fixtures?.[name] : undefined;
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
if (!resolve) {
|
||||
// Loud gap detection: build-time queries throw on GraphQL errors,
|
||||
// so a missing fixture fails `next build` with the name visible.
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
errors: [{ message: `mock-cms: no fixture for operation ${name}` }],
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
res.end(JSON.stringify({ data: resolve(body.variables ?? {}, state) }));
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`mock-cms: unhandled ${req.method} ${req.url}`);
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
} catch (err) {
|
||||
res.writeHead(500, { "content-type": "application/json" });
|
||||
res.end(JSON.stringify({ errors: [{ message: String(err) }] }));
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) =>
|
||||
server.listen(0, "127.0.0.1", () => resolve())
|
||||
);
|
||||
const { port } = server.address() as AddressInfo;
|
||||
mock.url = `http://127.0.0.1:${port}`;
|
||||
fixtures = makeFixtures(mock.url);
|
||||
return mock;
|
||||
}
|
||||
Reference in New Issue
Block a user