summaryrefslogtreecommitdiffhomepage
path: root/packages/transport-http/src/server.bun.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/transport-http/src/server.bun.test.ts')
-rw-r--r--packages/transport-http/src/server.bun.test.ts504
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
+ }
+ });
});