Files
2026-05-27 23:29:53 +01:00

128 lines
4.5 KiB
TypeScript

/**
* Integration test — exercises the Altair plugin's full surface against
* a real `Router` instance, end to end.
*
* Stage-7 spec notes the desired Playwright + Yoga integration; that
* arrives once `@suaveplan/api-graphql-yoga` lands. This file pins the
* structural integration the engine package will then layer Playwright
* on top of — HTML response, asset response, OTel observation, and
* collision detection — using only the dependencies actually present
* in the genesis tree today.
*/
import type { Counter, Meter, MeterProvider } from "@opentelemetry/api";
import { Router } from "@suaveplan/api-router";
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
createAltairInstruments,
createAltairUi,
GRAPHQL_UI_ALTAIR_CODES,
GraphqlUiAltairError,
} from "../../src/index.js";
interface FakeCounter {
readonly name: string;
addCalls: Array<{
value: number;
attrs?: Readonly<Record<string, unknown>>;
}>;
}
function buildFakeMeterProvider(): {
provider: MeterProvider;
counters: FakeCounter[];
} {
const counters: FakeCounter[] = [];
const provider: MeterProvider = {
getMeter(): Meter {
const meter: Meter = {
createCounter(n: string) {
const c: FakeCounter = { name: n, addCalls: [] };
counters.push(c);
return {
add(value, attrs) {
c.addCalls.push({ value, attrs });
},
} as Counter;
},
} as unknown as Meter;
return meter;
},
} as MeterProvider;
return { provider, counters };
}
describe("integration — altair end-to-end", () => {
it("GET /graphql/ui → 200 HTML with embedded config", {
timeout: 30000,
}, () => {
const { provider, counters } = buildFakeMeterProvider();
const instruments = createAltairInstruments({
meterProvider: provider,
});
const router = new Router();
const plugin = createAltairUi(undefined, { instruments });
plugin.mount(router);
const match = router.match("GET", "/graphql/ui");
expect(match).not.toBeNull();
const res = (match as NonNullable<typeof match>).handler() as {
status: number;
headers: Record<string, string>;
body: string;
};
expect(res.status).toBe(200);
expect(res.headers["Content-Type"]).toContain("text/html");
expect(res.body).toContain("Altair GraphQL Client");
expect(res.body).toContain('"graphqlEndpoint":"/graphql"');
// Counter observed.
expect(counters[0]?.name).toBe("graphql.ui.requests_total");
expect(counters[0]?.addCalls[0]?.attrs).toEqual({ ui: "altair" });
});
it("asset request → 200 with correct content-type and Cache-Control", {
timeout: 30000,
}, () => {
const router = new Router();
const plugin = createAltairUi();
plugin.mount(router);
const html = plugin.renderHtml().body;
const jsRef = html.match(
/\/graphql\/ui\/assets\/([0-9a-f]{16})\/altair\.js/
);
if (!jsRef) throw new Error("js hash not found");
const hash = jsRef[1] as string;
const m = router.match("GET", `/graphql/ui/assets/${hash}/altair.js`);
expect(m).not.toBeNull();
const res = (m as NonNullable<typeof m>).handler({
params: { hash, name: "altair.js" },
}) as {
status: number;
headers: Record<string, string>;
body: string;
};
expect(res.status).toBe(200);
expect(res.headers["Content-Type"]).toContain("application/javascript");
expect(res.headers["Cache-Control"]).toContain("immutable");
});
it("collision against pre-mounted GET /graphql/ui refused with typed error", {
timeout: 30000,
}, () => {
const router = new Router({ plugin: "first-owner" });
router.get("/graphql/ui", () => ({
status: 200,
headers: {},
body: "<other>",
streaming: false,
}));
try {
createAltairUi().mount(router);
throw new Error("expected throw");
} catch (err) {
expect(err).toBeInstanceOf(GraphqlUiAltairError);
expect((err as GraphqlUiAltairError).altairCode).toBe(
GRAPHQL_UI_ALTAIR_CODES.ENDPOINT_COLLISION
);
}
});
});