mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 23:02:04 +00:00
feat: sync universal schema and sdk updates
This commit is contained in:
parent
79bb441287
commit
f5d1a6383d
56 changed files with 6800 additions and 3974 deletions
305
sdks/typescript/tests/client.test.ts
Normal file
305
sdks/typescript/tests/client.test.ts
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
import { describe, it, expect, vi, type Mock } from "vitest";
|
||||
import { SandboxDaemonClient, SandboxDaemonError } from "../src/client.ts";
|
||||
|
||||
function createMockFetch(
|
||||
response: unknown,
|
||||
status = 200,
|
||||
headers: Record<string, string> = {}
|
||||
): Mock<typeof fetch> {
|
||||
return vi.fn<typeof fetch>().mockResolvedValue(
|
||||
new Response(JSON.stringify(response), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/json", ...headers },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function createMockFetchError(status: number, problem: unknown): Mock<typeof fetch> {
|
||||
return vi.fn<typeof fetch>().mockResolvedValue(
|
||||
new Response(JSON.stringify(problem), {
|
||||
status,
|
||||
headers: { "Content-Type": "application/problem+json" },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
describe("SandboxDaemonClient", () => {
|
||||
describe("constructor", () => {
|
||||
it("creates client with baseUrl", () => {
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
});
|
||||
expect(client).toBeInstanceOf(SandboxDaemonClient);
|
||||
});
|
||||
|
||||
it("strips trailing slash from baseUrl", async () => {
|
||||
const mockFetch = createMockFetch({ status: "ok" });
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080/",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.getHealth();
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/health",
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if fetch is not available", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
// @ts-expect-error - testing missing fetch
|
||||
globalThis.fetch = undefined;
|
||||
|
||||
expect(() => {
|
||||
new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
});
|
||||
}).toThrow("Fetch API is not available");
|
||||
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
});
|
||||
|
||||
describe("connect", () => {
|
||||
it("creates client without spawn when baseUrl provided", async () => {
|
||||
const client = await SandboxDaemonClient.connect({
|
||||
baseUrl: "http://localhost:8080",
|
||||
spawn: false,
|
||||
});
|
||||
expect(client).toBeInstanceOf(SandboxDaemonClient);
|
||||
});
|
||||
|
||||
it("throws when no baseUrl and spawn disabled", async () => {
|
||||
await expect(
|
||||
SandboxDaemonClient.connect({ spawn: false })
|
||||
).rejects.toThrow("baseUrl is required when autospawn is disabled");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHealth", () => {
|
||||
it("returns health response", async () => {
|
||||
const mockFetch = createMockFetch({ status: "ok" });
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const result = await client.getHealth();
|
||||
|
||||
expect(result).toEqual({ status: "ok" });
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/health",
|
||||
expect.objectContaining({ method: "GET" })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("listAgents", () => {
|
||||
it("returns agent list", async () => {
|
||||
const agents = { agents: [{ id: "claude", installed: true }] };
|
||||
const mockFetch = createMockFetch(agents);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const result = await client.listAgents();
|
||||
|
||||
expect(result).toEqual(agents);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createSession", () => {
|
||||
it("creates session with agent", async () => {
|
||||
const response = { healthy: true, agentSessionId: "abc123" };
|
||||
const mockFetch = createMockFetch(response);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const result = await client.createSession("test-session", {
|
||||
agent: "claude",
|
||||
});
|
||||
|
||||
expect(result).toEqual(response);
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test-session",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: JSON.stringify({ agent: "claude" }),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("encodes session ID in URL", async () => {
|
||||
const mockFetch = createMockFetch({ healthy: true });
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.createSession("test/session", { agent: "claude" });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test%2Fsession",
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("postMessage", () => {
|
||||
it("sends message to session", async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(
|
||||
new Response(null, { status: 204 })
|
||||
);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.postMessage("test-session", { message: "Hello" });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test-session/messages",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: "Hello" }),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEvents", () => {
|
||||
it("returns events", async () => {
|
||||
const events = { events: [], hasMore: false };
|
||||
const mockFetch = createMockFetch(events);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const result = await client.getEvents("test-session");
|
||||
|
||||
expect(result).toEqual(events);
|
||||
});
|
||||
|
||||
it("passes query parameters", async () => {
|
||||
const mockFetch = createMockFetch({ events: [], hasMore: false });
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.getEvents("test-session", { offset: 10, limit: 50 });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test-session/events?offset=10&limit=50",
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("authentication", () => {
|
||||
it("includes authorization header when token provided", async () => {
|
||||
const mockFetch = createMockFetch({ status: "ok" });
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
token: "test-token",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.getHealth();
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({
|
||||
headers: expect.any(Headers),
|
||||
})
|
||||
);
|
||||
|
||||
const call = mockFetch.mock.calls[0];
|
||||
const headers = call?.[1]?.headers as Headers | undefined;
|
||||
expect(headers?.get("Authorization")).toBe("Bearer test-token");
|
||||
});
|
||||
});
|
||||
|
||||
describe("error handling", () => {
|
||||
it("throws SandboxDaemonError on non-ok response", async () => {
|
||||
const problem = {
|
||||
type: "error",
|
||||
title: "Not Found",
|
||||
status: 404,
|
||||
detail: "Session not found",
|
||||
};
|
||||
const mockFetch = createMockFetchError(404, problem);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await expect(client.getEvents("nonexistent")).rejects.toThrow(
|
||||
SandboxDaemonError
|
||||
);
|
||||
|
||||
try {
|
||||
await client.getEvents("nonexistent");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(SandboxDaemonError);
|
||||
const error = e as SandboxDaemonError;
|
||||
expect(error.status).toBe(404);
|
||||
expect(error.problem?.title).toBe("Not Found");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("replyQuestion", () => {
|
||||
it("sends question reply", async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(
|
||||
new Response(null, { status: 204 })
|
||||
);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.replyQuestion("test-session", "q1", {
|
||||
answers: [["Yes"]],
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test-session/questions/q1/reply",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: JSON.stringify({ answers: [["Yes"]] }),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replyPermission", () => {
|
||||
it("sends permission reply", async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(
|
||||
new Response(null, { status: 204 })
|
||||
);
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
await client.replyPermission("test-session", "p1", {
|
||||
reply: "once",
|
||||
});
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
"http://localhost:8080/v1/sessions/test-session/permissions/p1/reply",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: JSON.stringify({ reply: "once" }),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
174
sdks/typescript/tests/integration.test.ts
Normal file
174
sdks/typescript/tests/integration.test.ts
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { type ChildProcess } from "node:child_process";
|
||||
import { SandboxDaemonClient } from "../src/client.ts";
|
||||
import { spawnSandboxDaemon, isNodeRuntime } from "../src/spawn.ts";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Check for binary in common locations
|
||||
function findBinary(): string | null {
|
||||
if (process.env.SANDBOX_AGENT_BIN) {
|
||||
return process.env.SANDBOX_AGENT_BIN;
|
||||
}
|
||||
|
||||
// Check cargo build output (run from sdks/typescript/tests)
|
||||
const cargoPaths = [
|
||||
resolve(__dirname, "../../../target/debug/sandbox-agent"),
|
||||
resolve(__dirname, "../../../target/release/sandbox-agent"),
|
||||
];
|
||||
|
||||
for (const p of cargoPaths) {
|
||||
if (existsSync(p)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const BINARY_PATH = findBinary();
|
||||
const SKIP_INTEGRATION = !BINARY_PATH && !process.env.RUN_INTEGRATION_TESTS;
|
||||
|
||||
// Set env var if we found a binary
|
||||
if (BINARY_PATH && !process.env.SANDBOX_AGENT_BIN) {
|
||||
process.env.SANDBOX_AGENT_BIN = BINARY_PATH;
|
||||
}
|
||||
|
||||
describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
|
||||
it("spawns daemon and connects", async () => {
|
||||
const handle = await spawnSandboxDaemon({
|
||||
enabled: true,
|
||||
log: "silent",
|
||||
timeoutMs: 30000,
|
||||
});
|
||||
|
||||
try {
|
||||
expect(handle.baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
|
||||
expect(handle.token).toBeTruthy();
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: handle.baseUrl,
|
||||
token: handle.token,
|
||||
});
|
||||
|
||||
const health = await client.getHealth();
|
||||
expect(health.status).toBe("ok");
|
||||
} finally {
|
||||
await handle.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it("SandboxDaemonClient.connect spawns automatically", async () => {
|
||||
const client = await SandboxDaemonClient.connect({
|
||||
spawn: { log: "silent", timeoutMs: 30000 },
|
||||
});
|
||||
|
||||
try {
|
||||
const health = await client.getHealth();
|
||||
expect(health.status).toBe("ok");
|
||||
|
||||
const agents = await client.listAgents();
|
||||
expect(agents.agents).toBeDefined();
|
||||
expect(Array.isArray(agents.agents)).toBe(true);
|
||||
} finally {
|
||||
await client.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it("lists available agents", async () => {
|
||||
const client = await SandboxDaemonClient.connect({
|
||||
spawn: { log: "silent", timeoutMs: 30000 },
|
||||
});
|
||||
|
||||
try {
|
||||
const agents = await client.listAgents();
|
||||
expect(agents.agents).toBeDefined();
|
||||
// Should have at least some agents defined
|
||||
expect(agents.agents.length).toBeGreaterThan(0);
|
||||
} finally {
|
||||
await client.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(SKIP_INTEGRATION)("Integration: connect (remote mode)", () => {
|
||||
let daemonProcess: ChildProcess;
|
||||
let baseUrl: string;
|
||||
let token: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Start daemon manually to simulate remote server
|
||||
const handle = await spawnSandboxDaemon({
|
||||
enabled: true,
|
||||
log: "silent",
|
||||
timeoutMs: 30000,
|
||||
});
|
||||
daemonProcess = handle.child;
|
||||
baseUrl = handle.baseUrl;
|
||||
token = handle.token;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (daemonProcess && daemonProcess.exitCode === null) {
|
||||
daemonProcess.kill("SIGTERM");
|
||||
await new Promise<void>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
daemonProcess.kill("SIGKILL");
|
||||
resolve();
|
||||
}, 5000);
|
||||
daemonProcess.once("exit", () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("connects to remote server", async () => {
|
||||
const client = await SandboxDaemonClient.connect({
|
||||
baseUrl,
|
||||
token,
|
||||
spawn: false,
|
||||
});
|
||||
|
||||
const health = await client.getHealth();
|
||||
expect(health.status).toBe("ok");
|
||||
});
|
||||
|
||||
it("creates client directly without spawn", () => {
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl,
|
||||
token,
|
||||
});
|
||||
expect(client).toBeInstanceOf(SandboxDaemonClient);
|
||||
});
|
||||
|
||||
it("handles authentication", async () => {
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl,
|
||||
token,
|
||||
});
|
||||
|
||||
const health = await client.getHealth();
|
||||
expect(health.status).toBe("ok");
|
||||
});
|
||||
|
||||
it("rejects invalid token on protected endpoints", async () => {
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl,
|
||||
token: "invalid-token",
|
||||
});
|
||||
|
||||
// Health endpoint may be open, but listing agents should require auth
|
||||
await expect(client.listAgents()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Runtime detection", () => {
|
||||
it("detects Node.js runtime", () => {
|
||||
expect(isNodeRuntime()).toBe(true);
|
||||
});
|
||||
});
|
||||
208
sdks/typescript/tests/sse-parser.test.ts
Normal file
208
sdks/typescript/tests/sse-parser.test.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
import { describe, it, expect, vi, type Mock } from "vitest";
|
||||
import { SandboxDaemonClient } from "../src/client.ts";
|
||||
import type { UniversalEvent } from "../src/types.ts";
|
||||
|
||||
function createMockResponse(chunks: string[]): Response {
|
||||
let chunkIndex = 0;
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (chunkIndex < chunks.length) {
|
||||
controller.enqueue(encoder.encode(chunks[chunkIndex]));
|
||||
chunkIndex++;
|
||||
} else {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}
|
||||
|
||||
function createMockFetch(chunks: string[]): Mock<typeof fetch> {
|
||||
return vi.fn<typeof fetch>().mockResolvedValue(createMockResponse(chunks));
|
||||
}
|
||||
|
||||
function createEvent(sequence: number): UniversalEvent {
|
||||
return {
|
||||
event_id: `evt-${sequence}`,
|
||||
sequence,
|
||||
session_id: "test-session",
|
||||
source: "agent",
|
||||
synthetic: false,
|
||||
time: new Date().toISOString(),
|
||||
type: "item.started",
|
||||
data: {
|
||||
item_id: `item-${sequence}`,
|
||||
kind: "message",
|
||||
role: "assistant",
|
||||
status: "in_progress",
|
||||
content: [],
|
||||
},
|
||||
} as UniversalEvent;
|
||||
}
|
||||
|
||||
describe("SSE Parser", () => {
|
||||
it("parses single SSE event", async () => {
|
||||
const event = createEvent(1);
|
||||
const mockFetch = createMockFetch([`data: ${JSON.stringify(event)}\n\n`]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].sequence).toBe(1);
|
||||
});
|
||||
|
||||
it("parses multiple SSE events", async () => {
|
||||
const event1 = createEvent(1);
|
||||
const event2 = createEvent(2);
|
||||
const mockFetch = createMockFetch([
|
||||
`data: ${JSON.stringify(event1)}\n\n`,
|
||||
`data: ${JSON.stringify(event2)}\n\n`,
|
||||
]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
expect(events[0].sequence).toBe(1);
|
||||
expect(events[1].sequence).toBe(2);
|
||||
});
|
||||
|
||||
it("handles chunked SSE data", async () => {
|
||||
const event = createEvent(1);
|
||||
const fullMessage = `data: ${JSON.stringify(event)}\n\n`;
|
||||
// Split in the middle of the message
|
||||
const mockFetch = createMockFetch([
|
||||
fullMessage.slice(0, 10),
|
||||
fullMessage.slice(10),
|
||||
]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].sequence).toBe(1);
|
||||
});
|
||||
|
||||
it("handles multiple events in single chunk", async () => {
|
||||
const event1 = createEvent(1);
|
||||
const event2 = createEvent(2);
|
||||
const mockFetch = createMockFetch([
|
||||
`data: ${JSON.stringify(event1)}\n\ndata: ${JSON.stringify(event2)}\n\n`,
|
||||
]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("ignores non-data lines", async () => {
|
||||
const event = createEvent(1);
|
||||
const mockFetch = createMockFetch([
|
||||
`: this is a comment\n`,
|
||||
`id: 1\n`,
|
||||
`data: ${JSON.stringify(event)}\n\n`,
|
||||
]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("handles CRLF line endings", async () => {
|
||||
const event = createEvent(1);
|
||||
const mockFetch = createMockFetch([
|
||||
`data: ${JSON.stringify(event)}\r\n\r\n`,
|
||||
]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("handles empty stream", async () => {
|
||||
const mockFetch = createMockFetch([]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
const events: UniversalEvent[] = [];
|
||||
for await (const e of client.streamEvents("test-session")) {
|
||||
events.push(e);
|
||||
}
|
||||
|
||||
expect(events).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("passes query parameters", async () => {
|
||||
const mockFetch = createMockFetch([]);
|
||||
|
||||
const client = new SandboxDaemonClient({
|
||||
baseUrl: "http://localhost:8080",
|
||||
fetch: mockFetch,
|
||||
});
|
||||
|
||||
// Consume the stream
|
||||
for await (const _ of client.streamEvents("test-session", { offset: 5 })) {
|
||||
// empty
|
||||
}
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith(
|
||||
expect.stringContaining("offset=5"),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue