Skip to main content
Glama
async-execution.test.ts9 kB
import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import * as grpc from '@grpc/grpc-js'; import { createSandboxService } from '../server/sandbox-service.js'; import { SandboxServiceService, ExecutionState } from '../generated/sandbox.js'; import { SandboxClient } from '../client/index.js'; import { existsSync, rmSync } from 'node:fs'; // Hardcoded ports - src uses 58xxx, dist uses 58500+ const isDistBuild = import.meta.url.includes('/dist/'); const PORT_BASE = isDistBuild ? 58500 : 58000; describe('Async Execution API', () => { const testCacheDir = '/tmp/prodisco-async-test-' + Date.now(); let server: grpc.Server; let client: SandboxClient; const port = PORT_BASE + 1; beforeAll(async () => { // Start server server = new grpc.Server(); const service = createSandboxService({ cacheDir: testCacheDir }); server.addService(SandboxServiceService, service); await new Promise<void>((resolve, reject) => { server.bindAsync(`localhost:${port}`, grpc.ServerCredentials.createInsecure(), (err) => { if (err) reject(err); else resolve(); }); }); // Create client client = new SandboxClient({ tcpHost: 'localhost', tcpPort: port }); // Wait for server const healthy = await client.waitForHealthy(5000); if (!healthy) throw new Error('Server did not become healthy'); }); afterAll(() => { client.close(); server.forceShutdown(); if (existsSync(testCacheDir)) { rmSync(testCacheDir, { recursive: true }); } }); describe('executeAsync()', () => { it('returns execution ID immediately', async () => { const code = ` await new Promise(r => setTimeout(r, 100)); console.log("done"); `; const start = Date.now(); const { executionId, state } = await client.executeAsync({ code }); const elapsed = Date.now() - start; // Should return immediately (not wait for execution) // This is best-effort and can vary across environments (gRPC handshake, CPU contention). // It should still be significantly faster than the actual execution. expect(elapsed).toBeLessThan(1000); expect(executionId).toMatch(/^[0-9a-f-]{36}$/); expect(state).toBe(ExecutionState.EXECUTION_STATE_PENDING); }); it('rejects invalid request', async () => { await expect(client.executeAsync({ cached: 'nonexistent' })) .rejects .toThrow(/not found/); }); }); describe('getExecution()', () => { it('returns current status of running execution', async () => { const code = ` console.log("line 1"); await new Promise(r => setTimeout(r, 200)); console.log("line 2"); `; const { executionId } = await client.executeAsync({ code }); // Give it time to start and produce output await new Promise(r => setTimeout(r, 50)); const status = await client.getExecution(executionId); expect(status.executionId).toBe(executionId); expect([ ExecutionState.EXECUTION_STATE_PENDING, ExecutionState.EXECUTION_STATE_RUNNING, ]).toContain(status.state); }); it('returns output incrementally with offset', async () => { const code = ` console.log("first"); await new Promise(r => setTimeout(r, 100)); console.log("second"); await new Promise(r => setTimeout(r, 100)); console.log("third"); `; const { executionId } = await client.executeAsync({ code }); // Wait for some output await new Promise(r => setTimeout(r, 150)); // Get first batch const status1 = await client.getExecution(executionId); const offset1 = status1.outputLength; // Wait for more output await new Promise(r => setTimeout(r, 150)); // Get incremental output const status2 = await client.getExecution(executionId, { outputOffset: offset1 }); // Should have new output if (status2.outputLength > offset1) { expect(status2.output.length).toBeGreaterThan(0); // First batch's content should not be included (only check if first batch had content) if (status1.output.length > 0) { expect(status2.output).not.toContain(status1.output.slice(0, 5)); } } }); it('returns NOT_FOUND for invalid execution ID', async () => { await expect(client.getExecution('invalid-id')) .rejects .toThrow(/not found/i); }); }); describe('waitForExecution()', () => { it('waits for execution to complete', async () => { const code = ` await new Promise(r => setTimeout(r, 100)); console.log("completed"); `; const { executionId } = await client.executeAsync({ code }); const status = await client.waitForExecution(executionId); expect(status.executionId).toBe(executionId); expect(status.state).toBe(ExecutionState.EXECUTION_STATE_COMPLETED); expect(status.result).toBeDefined(); expect(status.result?.success).toBe(true); }); it('returns immediately if already completed', async () => { const code = 'console.log("quick");'; const { executionId } = await client.executeAsync({ code }); // Wait for completion await new Promise(r => setTimeout(r, 200)); const start = Date.now(); const status = await client.waitForExecution(executionId); const elapsed = Date.now() - start; expect(status.state).toBe(ExecutionState.EXECUTION_STATE_COMPLETED); // Should return quickly since already done expect(elapsed).toBeLessThan(100); }); }); describe('cancelExecution()', () => { it('cancels a running execution', async () => { const code = ` for (let i = 0; i < 100; i++) { console.log("loop " + i); await new Promise(r => setTimeout(r, 50)); } `; const { executionId } = await client.executeAsync({ code }); // Wait for it to start await new Promise(r => setTimeout(r, 100)); // Cancel it const result = await client.cancelExecution(executionId); expect(result.success).toBe(true); expect(result.state).toBe(ExecutionState.EXECUTION_STATE_CANCELLED); }); it('returns failure for already completed execution', async () => { const code = 'console.log("quick");'; const { executionId } = await client.executeAsync({ code }); // Wait for completion await new Promise(r => setTimeout(r, 200)); const result = await client.cancelExecution(executionId); expect(result.success).toBe(false); expect(result.message).toContain('already finished'); }); it('returns NOT_FOUND for invalid execution ID', async () => { await expect(client.cancelExecution('invalid-id')) .rejects .toThrow(/not found/i); }); }); describe('listExecutions()', () => { it('lists recent executions', async () => { // Run a few executions await client.executeAsync({ code: 'console.log("exec 1");' }); await client.executeAsync({ code: 'console.log("exec 2");' }); await client.executeAsync({ code: 'console.log("exec 3");' }); // Wait for them to complete await new Promise(r => setTimeout(r, 200)); const executions = await client.listExecutions({ includeCompletedWithinMs: 60000, }); expect(executions.length).toBeGreaterThanOrEqual(3); executions.forEach(e => { expect(e.executionId).toBeDefined(); expect(e.state).toBeDefined(); expect(e.startedAtMs).toBeGreaterThan(0); }); }); it('filters by state', async () => { const { executionId } = await client.executeAsync({ code: ` for (let i = 0; i < 50; i++) { await new Promise(r => setTimeout(r, 20)); } `, }); // Give it time to start await new Promise(r => setTimeout(r, 50)); const running = await client.listExecutions({ states: [ExecutionState.EXECUTION_STATE_RUNNING], }); const hasRunning = running.some(e => e.executionId === executionId); expect(hasRunning).toBe(true); // Cancel it to clean up await client.cancelExecution(executionId); }); it('respects limit parameter', async () => { const executions = await client.listExecutions({ limit: 2, includeCompletedWithinMs: 60000, }); expect(executions.length).toBeLessThanOrEqual(2); }); it('includes code preview', async () => { const code = 'console.log("unique preview test 12345");'; await client.executeAsync({ code }); await new Promise(r => setTimeout(r, 200)); const executions = await client.listExecutions({ includeCompletedWithinMs: 60000, }); const found = executions.find(e => e.codePreview.includes('unique preview')); expect(found).toBeDefined(); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/harche/ProDisco'

If you have feedback or need assistance with the MCP directory API, please join our Discord server