diff options
Diffstat (limited to 'packages/transport-http/src/server.bun.test.ts')
| -rw-r--r-- | packages/transport-http/src/server.bun.test.ts | 504 |
1 files changed, 252 insertions, 252 deletions
diff --git a/packages/transport-http/src/server.bun.test.ts b/packages/transport-http/src/server.bun.test.ts index f552507..f93e259 100644 --- a/packages/transport-http/src/server.bun.test.ts +++ b/packages/transport-http/src/server.bun.test.ts @@ -3,305 +3,305 @@ import type { ConfigAccess, HostAPI, Logger } from "@dispatch/kernel"; import { createApp } from "./app.js"; import { createTransportHttpExtension } from "./index.js"; import type { - ConversationStore, - CredentialStore, - LspService, - SessionOrchestrator, + ConversationStore, + CredentialStore, + LspService, + SessionOrchestrator, } from "./seam.js"; function fakeLogger(): Logger { - return { - debug() {}, - info() {}, - warn() {}, - error() {}, - child() { - return fakeLogger(); - }, - span() { - return { - id: "fake-span", - log: fakeLogger(), - setAttributes() {}, - addLink() {}, - child() { - return this; - }, - end() {}, - }; - }, - }; + return { + debug() {}, + info() {}, + warn() {}, + error() {}, + child() { + return fakeLogger(); + }, + span() { + return { + id: "fake-span", + log: fakeLogger(), + setAttributes() {}, + addLink() {}, + child() { + return this; + }, + end() {}, + }; + }, + }; } function fakeConversationStore(): ConversationStore { - return { - async append() {}, - async load() { - return []; - }, - async loadSince() { - return []; - }, - async appendMetrics() {}, - async loadMetrics() { - return []; - }, - async getCwd() { - return null; - }, - async setCwd() {}, - async getReasoningEffort() { - return null; - }, - async setReasoningEffort() {}, - async listConversations() { - return []; - }, - async getConversationMeta() { - return null; - }, - async setConversationTitle() {}, - async getConversationStatus() { - return null; - }, - async setConversationStatus() {}, - async replaceHistory() {}, - async getCompactPercent() { - return null; - }, - async setCompactPercent() {}, - async forkHistory() {}, - async setCompactedFrom() {}, - async getWorkspace() { - return null; - }, - async ensureWorkspace() { - return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; - }, - async setWorkspaceTitle() { - return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; - }, - async setWorkspaceDefaultCwd() { - return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; - }, - async deleteWorkspace() { - return { closedCount: 0 }; - }, - async listWorkspaces() { - return []; - }, - async getWorkspaceId() { - return "default"; - }, - async setWorkspaceId() {}, - async getEffectiveCwd() { - return null; - }, - }; + return { + async append() {}, + async load() { + return []; + }, + async loadSince() { + return []; + }, + async appendMetrics() {}, + async loadMetrics() { + return []; + }, + async getCwd() { + return null; + }, + async setCwd() {}, + async getReasoningEffort() { + return null; + }, + async setReasoningEffort() {}, + async listConversations() { + return []; + }, + async getConversationMeta() { + return null; + }, + async setConversationTitle() {}, + async getConversationStatus() { + return null; + }, + async setConversationStatus() {}, + async replaceHistory() {}, + async getCompactPercent() { + return null; + }, + async setCompactPercent() {}, + async forkHistory() {}, + async setCompactedFrom() {}, + async getWorkspace() { + return null; + }, + async ensureWorkspace() { + return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; + }, + async setWorkspaceTitle() { + return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; + }, + async setWorkspaceDefaultCwd() { + return { id: "default", title: "default", defaultCwd: null, createdAt: 0, lastActivityAt: 0 }; + }, + async deleteWorkspace() { + return { closedCount: 0 }; + }, + async listWorkspaces() { + return []; + }, + async getWorkspaceId() { + return "default"; + }, + async setWorkspaceId() {}, + async getEffectiveCwd() { + return null; + }, + }; } function fakeOrchestrator(): SessionOrchestrator { - return { - startTurn() { - return { started: true, turnId: "fake-turn" }; - }, - subscribe() { - return () => {}; - }, - isActive() { - return false; - }, - enqueue() { - return { startedTurn: false, queue: [] }; - }, - closeConversation() { - return { abortedTurn: false }; - }, - stopTurn() { - return { abortedTurn: false }; - }, - async handleMessage() {}, - }; + return { + startTurn() { + return { started: true, turnId: "fake-turn" }; + }, + subscribe() { + return () => {}; + }, + isActive() { + return false; + }, + enqueue() { + return { startedTurn: false, queue: [] }; + }, + closeConversation() { + return { abortedTurn: false }; + }, + stopTurn() { + return { abortedTurn: false }; + }, + async handleMessage() {}, + }; } function fakeCredentialStore(): CredentialStore { - return { - resolve() { - return undefined; - }, - async getModelInfo() { - return undefined; - }, - async listCatalog() { - return []; - }, - }; + return { + resolve() { + return undefined; + }, + async getModelInfo() { + return undefined; + }, + async listCatalog() { + return []; + }, + }; } function fakeLspService(): LspService { - return { - async status() { - return []; - }, - }; + return { + async status() { + return []; + }, + }; } function fakeConfig(overrides: Record<string, unknown> = {}): ConfigAccess { - return { - get<T>(key: string): T | undefined { - return overrides[key] as T | undefined; - }, - getAll() { - return overrides; - }, - }; + return { + get<T>(key: string): T | undefined { + return overrides[key] as T | undefined; + }, + getAll() { + return overrides; + }, + }; } const SERVICES = new Map<string, unknown>([ - ["conversation-store/store", fakeConversationStore()], - ["session-orchestrator/orchestrator", fakeOrchestrator()], - ["credential-store/registry", fakeCredentialStore()], - ["lsp", fakeLspService()], + ["conversation-store/store", fakeConversationStore()], + ["session-orchestrator/orchestrator", fakeOrchestrator()], + ["credential-store/registry", fakeCredentialStore()], + ["lsp", fakeLspService()], ]); function createFakeHostAPI(configOverrides: Record<string, unknown> = {}): HostAPI { - return { - defineTool() {}, - defineProvider() {}, - defineAuth() {}, - on() { - return () => {}; - }, - emit() {}, - addFilter() { - return () => {}; - }, - async applyFilters(_hook, value) { - return value; - }, - provideService() {}, - getService(handle) { - return SERVICES.get(handle.id) as never; - }, - storage() { - return { - get: async () => null, - set: async () => {}, - delete: async () => {}, - has: async () => false, - keys: async () => [], - }; - }, - config: fakeConfig(configOverrides), - secrets: { get: async () => null, set: async () => {}, delete: async () => {} }, - permissions: { check: async () => ({ allowed: true }) }, - events: { emit() {} }, - logger: fakeLogger(), - getProviders() { - return new Map(); - }, - getTools() { - return new Map(); - }, - getAuthProviders() { - return new Map(); - }, - getAuthProvider() { - return undefined; - }, - getExtensions() { - return []; - }, - scheduler: { register() {} }, - }; + return { + defineTool() {}, + defineProvider() {}, + defineAuth() {}, + on() { + return () => {}; + }, + emit() {}, + addFilter() { + return () => {}; + }, + async applyFilters(_hook, value) { + return value; + }, + provideService() {}, + getService(handle) { + return SERVICES.get(handle.id) as never; + }, + storage() { + return { + get: async () => null, + set: async () => {}, + delete: async () => {}, + has: async () => false, + keys: async () => [], + }; + }, + config: fakeConfig(configOverrides), + secrets: { get: async () => null, set: async () => {}, delete: async () => {} }, + permissions: { check: async () => ({ allowed: true }) }, + events: { emit() {} }, + logger: fakeLogger(), + getProviders() { + return new Map(); + }, + getTools() { + return new Map(); + }, + getAuthProviders() { + return new Map(); + }, + getAuthProvider() { + return undefined; + }, + getExtensions() { + return []; + }, + scheduler: { register() {} }, + }; } // ── Server helper (mirrors extension.ts logic for direct testing) ──────── function startServer(port = 0) { - const app = createApp({ - conversationStore: fakeConversationStore(), - orchestrator: fakeOrchestrator(), - credentialStore: fakeCredentialStore(), - }); - return Bun.serve({ port, fetch: app.fetch }); + const app = createApp({ + conversationStore: fakeConversationStore(), + orchestrator: fakeOrchestrator(), + credentialStore: fakeCredentialStore(), + }); + return Bun.serve({ port, fetch: app.fetch }); } // ── Tests ──────────────────────────────────────────────────────────────── describe("serves HTTP on the configured port", () => { - let server: ReturnType<typeof Bun.serve>; - let port: number; + let server: ReturnType<typeof Bun.serve>; + let port: number; - afterEach(() => { - server.stop(); - }); + afterEach(() => { + server.stop(); + }); - test("GET /health returns 200", async () => { - server = startServer(); - port = server.port as number; + test("GET /health returns 200", async () => { + server = startServer(); + port = server.port as number; - const res = await fetch(`http://localhost:${port}/health`); - expect(res.status).toBe(200); - const body = (await res.json()) as { ok: boolean }; - expect(body.ok).toBe(true); - }); + const res = await fetch(`http://localhost:${port}/health`); + expect(res.status).toBe(200); + const body = (await res.json()) as { ok: boolean }; + expect(body.ok).toBe(true); + }); - test("POST /chat returns NDJSON", async () => { - server = startServer(); - port = server.port as number; + test("POST /chat returns NDJSON", async () => { + server = startServer(); + port = server.port as number; - const res = await fetch(`http://localhost:${port}/chat`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: "hi", conversationId: "conv1" }), - }); - expect(res.status).toBe(200); - expect(res.headers.get("Content-Type")).toBe("application/x-ndjson"); - }); + const res = await fetch(`http://localhost:${port}/chat`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ message: "hi", conversationId: "conv1" }), + }); + expect(res.status).toBe(200); + expect(res.headers.get("Content-Type")).toBe("application/x-ndjson"); + }); - test("GET /models returns 200", async () => { - server = startServer(); - port = server.port as number; + test("GET /models returns 200", async () => { + server = startServer(); + port = server.port as number; - const res = await fetch(`http://localhost:${port}/models`); - expect(res.status).toBe(200); - const body = (await res.json()) as { models: readonly string[] }; - expect(body.models).toEqual([]); - }); + const res = await fetch(`http://localhost:${port}/models`); + expect(res.status).toBe(200); + const body = (await res.json()) as { models: readonly string[] }; + expect(body.models).toEqual([]); + }); - test("GET /conversations/:id returns 200", async () => { - server = startServer(); - port = server.port as number; + test("GET /conversations/:id returns 200", async () => { + server = startServer(); + port = server.port as number; - const res = await fetch(`http://localhost:${port}/conversations/conv1`); - expect(res.status).toBe(200); - const body = (await res.json()) as { chunks: readonly unknown[]; latestSeq: number }; - expect(body.chunks).toEqual([]); - expect(body.latestSeq).toBe(0); - }); + const res = await fetch(`http://localhost:${port}/conversations/conv1`); + expect(res.status).toBe(200); + const body = (await res.json()) as { chunks: readonly unknown[]; latestSeq: number }; + expect(body.chunks).toEqual([]); + expect(body.latestSeq).toBe(0); + }); }); describe("extension lifecycle", () => { - test("activate starts server on config port and deactivate stops it", async () => { - const ext = createTransportHttpExtension(); - const host = createFakeHostAPI({ httpPort: 0 }); - await ext.activate(host); - const server = ext._testServer; - expect(server).toBeDefined(); - const port = server?.port as number; - expect(port).toBeGreaterThan(0); + test("activate starts server on config port and deactivate stops it", async () => { + const ext = createTransportHttpExtension(); + const host = createFakeHostAPI({ httpPort: 0 }); + await ext.activate(host); + const server = ext._testServer; + expect(server).toBeDefined(); + const port = server?.port as number; + expect(port).toBeGreaterThan(0); - const res = await fetch(`http://localhost:${port}/health`); - expect(res.status).toBe(200); + const res = await fetch(`http://localhost:${port}/health`); + expect(res.status).toBe(200); - ext.deactivate?.(); + ext.deactivate?.(); - try { - await fetch(`http://localhost:${port}/health`); - expect(true).toBe(false); - } catch { - // expected — server stopped - } - }); + try { + await fetch(`http://localhost:${port}/health`); + expect(true).toBe(false); + } catch { + // expected — server stopped + } + }); }); |
