import { expect, test } from "bun:test"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import type { AuthUser, Feature, ServerContext } from "@stc/contracts"; import { composeServer, resolveConfig } from "../src"; const FIXTURES = resolve(import.meta.dir, "../../../fixtures/courses"); const config = () => resolveConfig({ dataDir: mkdtempSync(join(tmpdir(), "stc-compose-")), databaseUrl: "sqlite://:memory:", courseDirs: [FIXTURES] }); test("factories get the context; the queue is built from the union of their jobs; auth and stop are honoured", async () => { const seen: string[] = []; let stopped = 0; const user: AuthUser = { id: "u", email: "u@x", name: "U", roles: ["owner"], groups: [] }; let early: unknown = null; const a = (ctx: ServerContext): Feature => { // the handle exists while factories run, but enqueuing must wait for the real queue try { void ctx.jobs.enqueue("b.work", {}); } catch (e) { early = e; } return { name: "a", routes: { "/api/a": { GET: async (_r, rc) => Response.json({ who: rc.user?.id ?? null, courses: ctx.courses.list().length }) } }, jobs: { "a.work": async (p) => { seen.push(`a:${JSON.stringify(p)}`); return null; } }, auth: { userFromRequest: async () => user }, stop: () => { stopped++; }, }; }; const b = (ctx: ServerContext): Feature => ({ name: "b", routes: { "/api/b": { POST: async () => Response.json({ id: await ctx.jobs.enqueue("a.work", { n: 1 }) }) } }, jobs: { "b.work": async () => null }, }); const s = await composeServer(config(), [a, b]); try { expect(String(early)).toMatch(/not ready/); expect(await (await s.app.fetch(new Request("http://x/api/a"))).json()).toEqual({ who: "u", courses: 1 }); const { id } = (await (await s.app.fetch(new Request("http://x/api/b", { method: "POST", headers: { origin: "http://localhost:3000" } }))).json()) as { id: string }; for (let i = 0; i < 200 && (await s.ctx.jobs.get(id))?.status !== "done"; i++) await Bun.sleep(10); expect((await s.ctx.jobs.get(id))?.status).toBe("done"); expect(seen).toEqual(['a:{"n":1}']); } finally { await s.stop(); } expect(stopped).toBe(1); }); test("duplicate job kinds and two auth services are refused", async () => { const f = (name: string, extra: Partial) => (): Feature => ({ name, routes: {}, ...extra }); await expect(composeServer(config(), [f("x", { jobs: { k: async () => null } }), f("y", { jobs: { k: async () => null } })])).rejects.toThrow(/Duplicate job kind "k"/); const auth = { userFromRequest: async () => null }; await expect(composeServer(config(), [f("x", { auth }), f("y", { auth })])).rejects.toThrow(/AuthService/); });