import { describe, expect, it, vi } from "vitest"; import worker from "../src/index"; import type { Env, FetcherLike } from "../src/env"; function createJsonService(status: number, body: unknown): FetcherLike { return { fetch: vi.fn(async () => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", }, }), ), }; } function createEnv(overrides: Partial = {}): Env { return { AUTH: createJsonService(401, { authenticated: false }), OPS: createJsonService(404, { service: "ops" }), SCHEDULER: createJsonService(404, { service: "scheduler" }), ...overrides, }; } describe("cfw-gateway worker", () => { it("returns health status", async () => { const env = createEnv(); const response = await worker.fetch(new Request("http://gateway.local/healthz"), env); expect(response.status).toBe(200); await expect(response.json()).resolves.toEqual({ ok: true, service: "cfw-gateway", }); }); it("proxies Better Auth routes to the auth service", async () => { const auth = createJsonService(200, { proxied: true }); const env = createEnv({ AUTH: auth, }); const response = await worker.fetch(new Request("http://gateway.local/api/auth/session"), env); expect(response.status).toBe(200); await expect(response.json()).resolves.toEqual({ proxied: true }); expect(auth.fetch).toHaveBeenCalledOnce(); }); it("proxies ops and scheduler routes with gateway prefixes removed", async () => { const ops = createJsonService(200, { proxied: "ops" }); const scheduler = createJsonService(200, { proxied: "scheduler" }); const env = createEnv({ OPS: ops, SCHEDULER: scheduler, }); const opsResponse = await worker.fetch( new Request("http://gateway.local/api/ops/observations?limit=10"), env, ); const schedulerResponse = await worker.fetch( new Request("http://gateway.local/api/scheduler/jobs"), env, ); expect(opsResponse.status).toBe(200); expect(schedulerResponse.status).toBe(200); const opsRequest = vi.mocked(ops.fetch).mock.calls[0]?.[0]; const schedulerRequest = vi.mocked(scheduler.fetch).mock.calls[0]?.[0]; expect(new URL(opsRequest.url).pathname).toBe("/observations"); expect(new URL(opsRequest.url).search).toBe("?limit=10"); expect(new URL(schedulerRequest.url).pathname).toBe("/jobs"); }); it("serves Scalar API docs for the aggregated schema", async () => { const response = await worker.fetch(new Request("http://gateway.local/docs"), createEnv()); expect(response.status).toBe(200); const html = await response.text(); expect(html).toContain("Scalar API Reference"); expect(html).toContain('"url": "/openapi.json"'); }); it("serves the aggregated OpenAPI schema", async () => { const env = createEnv({ AUTH: createJsonService(200, { openapi: "3.1.0", info: { title: "auth", version: "1.0.0" }, paths: { "/api/auth/sign-in/email": { post: { responses: { "200": { description: "ok" } } } } }, }), OPS: createJsonService(200, { openapi: "3.1.0", info: { title: "ops", version: "1.0.0" }, paths: { "/observations": { post: { responses: { "202": { description: "accepted" } } } } }, }), SCHEDULER: createJsonService(200, { openapi: "3.1.0", info: { title: "scheduler", version: "1.0.0" }, paths: { "/jobs": { get: { responses: { "200": { description: "ok" } } } } }, }), }); const response = await worker.fetch(new Request("http://gateway.local/openapi.json"), env); expect(response.status).toBe(200); const schema = await response.json() as { info: { title: string }; paths: Record }; expect(schema.info.title).toBe("cfw-gateway API"); expect(schema.paths["/api/auth/sign-in/email"]).toBeTruthy(); expect(schema.paths["/api/ops/observations"]).toBeTruthy(); expect(schema.paths["/api/scheduler/jobs"]).toBeTruthy(); }); it("serves the gateway-owned plugin manifest without consulting services", async () => { const auth = createJsonService(401, { authenticated: false }); const ops = createJsonService(404, { service: "ops" }); const scheduler = createJsonService(404, { service: "scheduler" }); const env = createEnv({ AUTH: auth, OPS: ops, SCHEDULER: scheduler, }); const response = await worker.fetch(new Request("http://gateway.local/mf-manifest.json"), env); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toContain("application/json"); expect(response.headers.get("cache-control")).toBe("public, max-age=60, stale-while-revalidate=300"); const manifest = await response.json() as { schemaVersion: number; kind: string; plugins: Array<{ name: string; entry: string; routes: string[]; status: string; }>; }; expect(manifest.schemaVersion).toBe(1); expect(manifest.kind).toBe("cfw-plugin-registry"); expect(manifest.plugins).toEqual( expect.arrayContaining([ expect.objectContaining({ name: "account-center", entry: "account_center@/apps/account-center/mf-manifest.json", routes: ["/account-center/*"], status: "enabled", }), ]), ); expect(auth.fetch).not.toHaveBeenCalled(); expect(ops.fetch).not.toHaveBeenCalled(); expect(scheduler.fetch).not.toHaveBeenCalled(); }); it("returns a diagnostic error when aggregated OpenAPI fetch fails", async () => { const env = createEnv({ AUTH: { fetch: vi.fn(async () => new Response("auth unavailable", { status: 503 })), }, }); const response = await worker.fetch(new Request("http://gateway.local/openapi.json"), env); expect(response.status).toBe(502); await expect(response.json()).resolves.toEqual({ ok: false, error: "openapi_aggregation_failed", message: "Failed to fetch auth OpenAPI schema: 503", }); }); it("returns not found for unknown routes without consulting auth service", async () => { const auth = createJsonService(401, { authenticated: false }); const env = createEnv({ AUTH: auth, }); const response = await worker.fetch(new Request("http://gateway.local/app"), env); expect(response.status).toBe(404); await expect(response.json()).resolves.toEqual({ ok: false, error: "not_found", }); expect(auth.fetch).not.toHaveBeenCalled(); }); });