108 lines
3.8 KiB
TypeScript
108 lines
3.8 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
/**
|
|
* Stage-5 smoke spec — exercises every wired adapter from a real HTTP
|
|
* call against a `createTestHost(...)`-backed server. The companion
|
|
* `src/server.ts` boots the Bun.serve listener; this spec hits the
|
|
* endpoints and asserts on the observable behaviour.
|
|
*/
|
|
|
|
test.describe("createTestHost — Stage-5 smoke", () => {
|
|
test("1. zero-config plugin install lands on host.plugins", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/api/plugins");
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as { plugins: string[] };
|
|
expect(body.plugins).toContain("smoke-fixture");
|
|
});
|
|
|
|
test("2. fixture-plugin route resolves via the in-memory router", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/api/principal");
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as {
|
|
id: string | null;
|
|
scopes: string[] | null;
|
|
};
|
|
// Even with no tenant header, principal still resolves through the
|
|
// seeded static-principal resolver.
|
|
expect(body.id).toBe("smoke-principal");
|
|
});
|
|
|
|
test("3. seed.principal reaches ctx.principal", async ({ request }) => {
|
|
const res = await request.get("/api/principal");
|
|
const body = (await res.json()) as {
|
|
id: string | null;
|
|
scopes: string[] | null;
|
|
};
|
|
expect(body.id).toBe("smoke-principal");
|
|
expect(body.scopes).toEqual(["smoke:read", "smoke:write"]);
|
|
});
|
|
|
|
test("4. seed.tenants + X-Tenant-Id reaches ctx.tenant", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/api/tenant", {
|
|
headers: { "X-Tenant-Id": "smoke-tenant" },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as {
|
|
id: string | null;
|
|
slug: string | null;
|
|
};
|
|
expect(body.id).toBe("smoke-tenant");
|
|
expect(body.slug).toBe("smoke");
|
|
});
|
|
|
|
test("5. seed.secrets exposes ctx.secrets.require", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/api/secret");
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as { name: string; length: number };
|
|
expect(body.name).toBe("SMOKE_TOKEN");
|
|
expect(body.length).toBe("shh-very-secret-smoke".length);
|
|
});
|
|
|
|
test("6. ctx.db round-trips a real bun:sqlite write/read", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/api/db", {
|
|
headers: { "X-Tenant-Id": "smoke-tenant" },
|
|
});
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as {
|
|
ok: boolean;
|
|
count: number;
|
|
first: string | null;
|
|
};
|
|
expect(body.ok).toBe(true);
|
|
expect(body.count).toBeGreaterThanOrEqual(1);
|
|
expect(body.first).toBe("hello");
|
|
});
|
|
|
|
test("7. handler emits an event observable via the in-memory bus", async ({
|
|
request,
|
|
}) => {
|
|
// Fire-and-forget — the in-memory bus captures the publish in
|
|
// history, so a subsequent endpoint (or `waitForEvent` in a
|
|
// shared-process consumer) can resolve against it.
|
|
const res = await request.get("/api/events/emit");
|
|
expect(res.status()).toBe(200);
|
|
const body = (await res.json()) as { ok: boolean; topic: string };
|
|
expect(body.ok).toBe(true);
|
|
expect(body.topic).toBe("smoke.events.fired");
|
|
});
|
|
|
|
test("8. host serves /internal/livez during the smoke", async ({
|
|
request,
|
|
}) => {
|
|
const res = await request.get("/internal/livez");
|
|
expect(res.status()).toBe(200);
|
|
const body = await res.text();
|
|
expect(body).toBe("ok");
|
|
});
|
|
});
|