Skip to main content
Glama
cluster-integration.test.ts35.5 kB
/** * Cluster Integration Tests * * These tests require a running Kubernetes cluster and Prometheus. * They are skipped if no cluster is available. * * To run these tests: * 1. Ensure kubectl is configured with a valid context * 2. Ensure Prometheus is running (port-forward to localhost:9090) * 3. Run: npm test -- --grep "Cluster Integration" */ import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import * as grpc from '@grpc/grpc-js'; import { existsSync, rmSync, unlinkSync } from 'node:fs'; import { execSync } from 'node:child_process'; import { startServer } from '../server/index.js'; import { SandboxClient } from '../client/index.js'; // Check if cluster is available function isClusterAvailable(): boolean { try { execSync('kubectl cluster-info', { stdio: 'pipe' }); return true; } catch { return false; } } // Check if Prometheus is available function isPrometheusAvailable(): boolean { try { execSync('curl -s http://127.0.0.1:9090/-/healthy', { stdio: 'pipe' }); return true; } catch { return false; } } const clusterAvailable = isClusterAvailable(); const prometheusAvailable = isPrometheusAvailable(); const K8S_SETUP = ` const k8s = require('@kubernetes/client-node'); const kc = new k8s.KubeConfig(); kc.loadFromDefault(); // client-node return shapes vary by version (some return { body }, some return body directly) const unwrap = (res) => (res && res.body ? res.body : res); `; // Generate unique IDs for test isolation (avoid collision between src and dist tests) const clusterTestId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; describe.skipIf(!clusterAvailable)('Cluster Integration Tests', () => { const testSocketPath = `/tmp/prodisco-cluster-test-${clusterTestId}.sock`; const testCacheDir = `/tmp/prodisco-cache-cluster-${clusterTestId}`; let server: grpc.Server; let client: SandboxClient; beforeAll(async () => { // Start the server with Prometheus URL if available server = await startServer({ socketPath: testSocketPath, cacheDir: testCacheDir, prometheusUrl: prometheusAvailable ? 'http://localhost:9090' : undefined, }); client = new SandboxClient({ socketPath: testSocketPath }); const healthy = await client.waitForHealthy(10000); expect(healthy).toBe(true); }); afterAll(async () => { client.close(); await new Promise<void>((resolve) => { server.tryShutdown((err) => { if (err) { server.forceShutdown(); } resolve(); }); }); if (existsSync(testSocketPath)) { try { unlinkSync(testSocketPath); } catch { // Ignore } } if (existsSync(testCacheDir)) { rmSync(testCacheDir, { recursive: true }); } }); describe('Kubernetes API - Namespaces', () => { it('lists namespaces', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNamespace()); const names = response.items.map(ns => ns.metadata?.name).filter(Boolean); console.log(JSON.stringify(names)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const namespaces = JSON.parse(result.output); expect(Array.isArray(namespaces)).toBe(true); expect(namespaces).toContain('default'); expect(namespaces).toContain('kube-system'); }); it('gets a specific namespace', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.readNamespace({ name: 'default' })); console.log(JSON.stringify({ name: response.metadata?.name, status: response.status?.phase, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const namespace = JSON.parse(result.output); expect(namespace.name).toBe('default'); expect(namespace.status).toBe('Active'); }); }); describe('Kubernetes API - Pods', () => { it('lists pods in kube-system', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNamespacedPod({ namespace: 'kube-system' })); const pods = response.items.map(pod => ({ name: pod.metadata?.name, status: pod.status?.phase, })); console.log(JSON.stringify(pods)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const pods = JSON.parse(result.output); expect(Array.isArray(pods)).toBe(true); expect(pods.length).toBeGreaterThan(0); // All pods should have a status pods.forEach((pod: { name: string; status: string }) => { expect(pod.name).toBeDefined(); expect(pod.status).toBeDefined(); }); }); it('lists pods across all namespaces', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listPodForAllNamespaces()); console.log(response.items.length); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const podCount = parseInt(result.output, 10); expect(podCount).toBeGreaterThan(0); }); }); describe('Kubernetes API - Nodes', () => { it('lists cluster nodes', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNode()); const nodes = response.items.map(node => ({ name: node.metadata?.name, ready: node.status?.conditions?.find(c => c.type === 'Ready')?.status === 'True', })); console.log(JSON.stringify(nodes)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const nodes = JSON.parse(result.output); expect(Array.isArray(nodes)).toBe(true); expect(nodes.length).toBeGreaterThan(0); // At least one node should be ready expect(nodes.some((n: { ready: boolean }) => n.ready)).toBe(true); }); it('gets node resources', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNode()); const node = response.items[0]; const resources = { cpu: node.status?.capacity?.cpu, memory: node.status?.capacity?.memory, }; console.log(JSON.stringify(resources)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const resources = JSON.parse(result.output); expect(resources.cpu).toBeDefined(); expect(resources.memory).toBeDefined(); }); }); describe('Kubernetes API - Services', () => { it('lists services in default namespace', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNamespacedService({ namespace: 'default' })); const services = response.items.map(svc => ({ name: svc.metadata?.name, type: svc.spec?.type, clusterIP: svc.spec?.clusterIP, })); console.log(JSON.stringify(services)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const services = JSON.parse(result.output); expect(Array.isArray(services)).toBe(true); // kubernetes service should always exist in default namespace expect(services.some((s: { name: string }) => s.name === 'kubernetes')).toBe(true); }); }); describe('Kubernetes API - Deployments', () => { it('lists deployments in kube-system', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.AppsV1Api); const response = unwrap(await api.listNamespacedDeployment({ namespace: 'kube-system' })); const deployments = response.items.map(dep => ({ name: dep.metadata?.name, replicas: dep.spec?.replicas, available: dep.status?.availableReplicas, })); console.log(JSON.stringify(deployments)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const deployments = JSON.parse(result.output); expect(Array.isArray(deployments)).toBe(true); // coredns deployment should exist in kube-system expect(deployments.some((d: { name: string }) => d.name === 'coredns')).toBe(true); }); }); describe('Kubernetes API - ConfigMaps', () => { it('lists configmaps in kube-system', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNamespacedConfigMap({ namespace: 'kube-system' })); const configmaps = response.items.map(cm => cm.metadata?.name).filter(Boolean); console.log(JSON.stringify(configmaps)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const configmaps = JSON.parse(result.output); expect(Array.isArray(configmaps)).toBe(true); expect(configmaps.length).toBeGreaterThan(0); }); }); describe('Kubernetes API - Events', () => { it('lists recent events', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listEventForAllNamespaces({ limit: 10 })); const events = response.items.map(event => ({ type: event.type, reason: event.reason, message: event.message?.substring(0, 100), })); console.log(JSON.stringify(events)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const events = JSON.parse(result.output); expect(Array.isArray(events)).toBe(true); }); }); describe('Kubernetes Context', () => { it('returns current context in health check', async () => { const health = await client.healthCheck(); expect(health.healthy).toBe(true); expect(health.kubernetesContext).toBeDefined(); expect(typeof health.kubernetesContext).toBe('string'); }); it('can access current context from sandbox', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} const context = kc.getCurrentContext(); console.log(context); `, }); expect(result.success).toBe(true); expect(result.output.length).toBeGreaterThan(0); }); }); }); const promTestId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; describe.skipIf(!clusterAvailable || !prometheusAvailable)('Prometheus Integration Tests', () => { const testSocketPath = `/tmp/prodisco-prom-test-${promTestId}.sock`; const testCacheDir = `/tmp/prodisco-cache-prom-${promTestId}`; let server: grpc.Server; let client: SandboxClient; beforeAll(async () => { server = await startServer({ socketPath: testSocketPath, cacheDir: testCacheDir, prometheusUrl: 'http://127.0.0.1:9090', }); client = new SandboxClient({ socketPath: testSocketPath }); const healthy = await client.waitForHealthy(10000); expect(healthy).toBe(true); }); afterAll(async () => { client.close(); await new Promise<void>((resolve) => { server.tryShutdown((err) => { if (err) { server.forceShutdown(); } resolve(); }); }); if (existsSync(testSocketPath)) { try { unlinkSync(testSocketPath); } catch { // Ignore } } if (existsSync(testCacheDir)) { rmSync(testCacheDir, { recursive: true }); } }); describe('Prometheus Queries', () => { it('queries up metric', async () => { const result = await client.execute({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const response = await prom.execute('up'); console.log(JSON.stringify({ resultType: response.resultType, resultCount: response.data.length, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const data = JSON.parse(result.output); expect(data.resultType).toBe('vector'); expect(data.resultCount).toBeGreaterThan(0); }); it('queries container metrics', async () => { const result = await client.execute({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const response = await prom.execute('container_cpu_usage_seconds_total'); console.log(JSON.stringify({ resultType: response.resultType, hasResults: response.data.length > 0, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const data = JSON.parse(result.output); expect(data.resultType).toBe('vector'); }); it('queries container memory metrics', async () => { const result = await client.execute({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const response = await prom.execute('container_memory_usage_bytes{container!=""}'); console.log(JSON.stringify({ resultType: response.resultType, containerCount: response.data.length, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const data = JSON.parse(result.output); expect(data.resultType).toBe('vector'); expect(data.containerCount).toBeGreaterThan(0); }); it('performs range query', async () => { const result = await client.execute({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const end = new Date(); const start = new Date(end.getTime() - 5 * 60 * 1000); // 5 minutes ago const response = await prom.executeRange('up', { start, end, step: 60 }); console.log(JSON.stringify({ resultType: response.resultType, seriesCount: response.data.length, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const data = JSON.parse(result.output); expect(data.resultType).toBe('matrix'); expect(data.seriesCount).toBeGreaterThan(0); }); it('queries node metrics', async () => { const result = await client.execute({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const response = await prom.execute('node_memory_MemTotal_bytes'); const nodes = response.data.map(r => ({ instance: r.labels.instance, memoryBytes: r.samples.length > 0 ? r.samples[0].value : 0, })); console.log(JSON.stringify(nodes)); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const nodes = JSON.parse(result.output); expect(Array.isArray(nodes)).toBe(true); }); }); describe('Combined K8s + Prometheus', () => { it('correlates pod info with container metrics', async () => { const result = await client.execute({ code: ` ${K8S_SETUP} // Get pods from Kubernetes const api = kc.makeApiClient(k8s.CoreV1Api); const podsResponse = unwrap(await api.listNamespacedPod({ namespace: 'kube-system', limit: 5 })); const podNames = podsResponse.items.map(p => p.metadata?.name).filter(Boolean); // Query Prometheus for container CPU metrics (available from cadvisor) const { PrometheusClient } = require('@prodisco/prometheus-client'); const prom = new PrometheusClient({ endpoint: 'http://127.0.0.1:9090', }); const response = await prom.execute('container_cpu_usage_seconds_total{container!=""}'); console.log(JSON.stringify({ k8sPodCount: podNames.length, prometheusMetricCount: response.data.length, })); `, timeoutMs: 30000, }); expect(result.success).toBe(true); const data = JSON.parse(result.output); expect(data.k8sPodCount).toBeGreaterThan(0); expect(data.prometheusMetricCount).toBeGreaterThan(0); }); }); }); const streamTestId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; describe.skipIf(!clusterAvailable)('Streaming with Real K8s Calls', () => { const testSocketPath = `/tmp/prodisco-stream-k8s-${streamTestId}.sock`; const testCacheDir = `/tmp/prodisco-cache-stream-k8s-${streamTestId}`; let server: grpc.Server; let client: SandboxClient; beforeAll(async () => { server = await startServer({ socketPath: testSocketPath, cacheDir: testCacheDir, }); client = new SandboxClient({ socketPath: testSocketPath }); const healthy = await client.waitForHealthy(10000); expect(healthy).toBe(true); }); afterAll(async () => { client.close(); await new Promise<void>((resolve) => { server.tryShutdown((err) => { if (err) { server.forceShutdown(); } resolve(); }); }); if (existsSync(testSocketPath)) { try { unlinkSync(testSocketPath); } catch { // Ignore } } if (existsSync(testCacheDir)) { rmSync(testCacheDir, { recursive: true }); } }); it('streams output while listing pods', async () => { const code = ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); console.log("Fetching pods..."); const response = unwrap(await api.listPodForAllNamespaces()); console.log("Found " + response.items.length + " pods"); for (const pod of response.items.slice(0, 3)) { console.log("Pod: " + pod.metadata?.name); } console.log("Done!"); `; const chunks: string[] = []; let result: any = null; for await (const chunk of client.executeStream({ code, timeoutMs: 30000 })) { if (chunk.type === 'output') { chunks.push(chunk.data as string); } else if (chunk.type === 'result') { result = chunk.data; } } expect(chunks.length).toBeGreaterThan(0); expect(chunks.join('')).toContain('Fetching pods'); expect(chunks.join('')).toContain('Found'); expect(chunks.join('')).toContain('Done!'); expect(result?.success).toBe(true); }); it('handles async execution with real K8s calls', async () => { const { executionId, state } = await client.executeAsync({ code: ` ${K8S_SETUP} const api = kc.makeApiClient(k8s.CoreV1Api); const response = unwrap(await api.listNamespace()); console.log("Namespaces: " + response.items.length); `, timeoutMs: 30000, }); expect(executionId).toBeDefined(); // Wait for completion const status = await client.waitForExecution(executionId); expect(status.state).toBe(3); // COMPLETED expect(status.result?.success).toBe(true); expect(status.output).toContain('Namespaces:'); }); }); // ============================================================================= // Comprehensive Streaming and Async Execution Tests // ============================================================================= const asyncStreamTestId = `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; describe('Streaming and Async Execution - Comprehensive Tests', () => { const testSocketPath = `/tmp/prodisco-async-stream-${asyncStreamTestId}.sock`; const testCacheDir = `/tmp/prodisco-cache-async-stream-${asyncStreamTestId}`; let server: grpc.Server; let client: SandboxClient; beforeAll(async () => { server = await startServer({ socketPath: testSocketPath, cacheDir: testCacheDir, }); client = new SandboxClient({ socketPath: testSocketPath }); const healthy = await client.waitForHealthy(10000); expect(healthy).toBe(true); }); afterAll(async () => { client.close(); await new Promise<void>((resolve) => { server.tryShutdown((err) => { if (err) { server.forceShutdown(); } resolve(); }); }); if (existsSync(testSocketPath)) { try { unlinkSync(testSocketPath); } catch { // Ignore } } if (existsSync(testCacheDir)) { rmSync(testCacheDir, { recursive: true }); } }); describe('ExecuteStream', () => { it('streams incremental output', async () => { const code = ` for (let i = 0; i < 5; i++) { console.log("Iteration " + i); await new Promise(r => setTimeout(r, 20)); } `; const chunks: string[] = []; const timestamps: number[] = []; for await (const chunk of client.executeStream({ code, timeoutMs: 10000 })) { if (chunk.type === 'output') { chunks.push(chunk.data as string); timestamps.push(chunk.timestampMs); } } // Should have received multiple chunks expect(chunks.length).toBeGreaterThan(1); // All iterations should be present const fullOutput = chunks.join(''); expect(fullOutput).toContain('Iteration 0'); expect(fullOutput).toContain('Iteration 4'); }); it('separates stdout and stderr', async () => { const code = ` console.log("Standard output"); console.error("Error output"); console.log("More standard output"); `; let stdout = ''; let stderr = ''; for await (const chunk of client.executeStream({ code, timeoutMs: 5000 })) { if (chunk.type === 'output') { stdout += chunk.data as string; } else if (chunk.type === 'error') { stderr += chunk.data as string; } } expect(stdout).toContain('Standard output'); expect(stdout).toContain('More standard output'); expect(stderr).toContain('Error output'); }); it('returns final result chunk', async () => { const code = 'console.log("Hello");'; let resultChunk: any = null; for await (const chunk of client.executeStream({ code, timeoutMs: 5000 })) { if (chunk.type === 'result') { resultChunk = chunk.data; } } expect(resultChunk).toBeDefined(); expect(resultChunk.success).toBe(true); expect(resultChunk.executionTimeMs).toBeGreaterThanOrEqual(0); }); it('handles execution failure in stream', async () => { const code = 'throw new Error("Stream failure test");'; let resultChunk: any = null; for await (const chunk of client.executeStream({ code, timeoutMs: 5000 })) { if (chunk.type === 'result') { resultChunk = chunk.data; } } expect(resultChunk).toBeDefined(); expect(resultChunk.success).toBe(false); expect(resultChunk.error).toContain('Stream failure test'); }); it('streams large output efficiently', async () => { const code = ` for (let i = 0; i < 100; i++) { console.log("Line " + i + ": " + "x".repeat(50)); } `; const chunks: string[] = []; for await (const chunk of client.executeStream({ code, timeoutMs: 10000 })) { if (chunk.type === 'output') { chunks.push(chunk.data as string); } } const fullOutput = chunks.join(''); expect(fullOutput).toContain('Line 0'); expect(fullOutput).toContain('Line 99'); // Should have received multiple chunks for large output expect(chunks.length).toBeGreaterThan(1); }); }); describe('ExecuteAsync', () => { it('returns execution ID immediately', async () => { const startTime = Date.now(); const { executionId, state } = await client.executeAsync({ code: ` await new Promise(r => setTimeout(r, 500)); console.log("Delayed execution"); `, timeoutMs: 10000, }); const duration = Date.now() - startTime; // Should return quickly (before the code completes) expect(duration).toBeLessThan(200); expect(executionId).toBeDefined(); expect(executionId.length).toBeGreaterThan(0); }); it('can poll for status', async () => { const { executionId } = await client.executeAsync({ code: ` console.log("Starting"); await new Promise(r => setTimeout(r, 200)); console.log("Done"); `, timeoutMs: 10000, }); // Poll immediately - might be running const status1 = await client.getExecution(executionId); expect(status1.executionId).toBe(executionId); // Wait and poll again - should be completed await new Promise(r => setTimeout(r, 300)); const status2 = await client.getExecution(executionId); expect(status2.state).toBe(3); // COMPLETED expect(status2.output).toContain('Done'); }); it('supports long-poll with wait', async () => { const { executionId } = await client.executeAsync({ code: ` await new Promise(r => setTimeout(r, 100)); console.log("Finished"); `, timeoutMs: 10000, }); // This should block until completion const status = await client.getExecution(executionId, { wait: true }); expect(status.state).toBe(3); // COMPLETED expect(status.output).toContain('Finished'); expect(status.result).toBeDefined(); expect(status.result?.success).toBe(true); }); it('supports incremental output reading', async () => { const { executionId } = await client.executeAsync({ code: ` console.log("Part 1"); await new Promise(r => setTimeout(r, 50)); console.log("Part 2"); await new Promise(r => setTimeout(r, 50)); console.log("Part 3"); `, timeoutMs: 10000, }); // Wait for completion await new Promise(r => setTimeout(r, 200)); // Read full output const status1 = await client.getExecution(executionId); expect(status1.output).toContain('Part 1'); expect(status1.output).toContain('Part 3'); // Read with offset (from middle) const halfOffset = Math.floor(status1.outputLength / 2); const status2 = await client.getExecution(executionId, { outputOffset: halfOffset }); // Should have less output when reading from offset expect(status2.output.length).toBeLessThan(status1.output.length); }); it('handles concurrent async executions', async () => { // Start 3 concurrent executions const [exec1, exec2, exec3] = await Promise.all([ client.executeAsync({ code: 'console.log("Exec 1");', timeoutMs: 5000 }), client.executeAsync({ code: 'console.log("Exec 2");', timeoutMs: 5000 }), client.executeAsync({ code: 'console.log("Exec 3");', timeoutMs: 5000 }), ]); // All should have unique IDs expect(exec1.executionId).not.toBe(exec2.executionId); expect(exec2.executionId).not.toBe(exec3.executionId); // Wait for all to complete const [status1, status2, status3] = await Promise.all([ client.waitForExecution(exec1.executionId), client.waitForExecution(exec2.executionId), client.waitForExecution(exec3.executionId), ]); expect(status1.state).toBe(3); // COMPLETED expect(status2.state).toBe(3); expect(status3.state).toBe(3); expect(status1.output).toContain('Exec 1'); expect(status2.output).toContain('Exec 2'); expect(status3.output).toContain('Exec 3'); }); }); describe('CancelExecution', () => { it('cancels a running execution', async () => { const { executionId } = await client.executeAsync({ code: ` for (let i = 0; i < 100; i++) { console.log("Iteration " + i); await new Promise(r => setTimeout(r, 100)); } `, timeoutMs: 30000, }); // Wait briefly for execution to start await new Promise(r => setTimeout(r, 150)); // Cancel the execution const cancelResult = await client.cancelExecution(executionId); expect(cancelResult.success).toBe(true); expect(cancelResult.state).toBe(5); // CANCELLED // Verify the execution is cancelled const status = await client.getExecution(executionId); expect(status.state).toBe(5); // CANCELLED }); it('handles cancellation of already completed execution', async () => { const { executionId } = await client.executeAsync({ code: 'console.log("Quick");', timeoutMs: 5000, }); // Wait for completion await client.waitForExecution(executionId); // Try to cancel completed execution const cancelResult = await client.cancelExecution(executionId); // Should fail or indicate already completed expect([3, 5]).toContain(cancelResult.state); // COMPLETED or CANCELLED }); }); describe('ListExecutions', () => { it('lists active executions', async () => { // Start a long-running execution const { executionId } = await client.executeAsync({ code: ` for (let i = 0; i < 20; i++) { await new Promise(r => setTimeout(r, 100)); } `, timeoutMs: 10000, }); await new Promise(r => setTimeout(r, 50)); // List running executions const executions = await client.listExecutions({ states: [2], // RUNNING }); // Should find our execution const found = executions.find(e => e.executionId === executionId); expect(found).toBeDefined(); expect(found?.state).toBe(2); // RUNNING // Cancel to clean up await client.cancelExecution(executionId); }); it('lists recent completed executions', async () => { // Execute something const { executionId } = await client.executeAsync({ code: 'console.log("List test");', timeoutMs: 5000, }); await client.waitForExecution(executionId); // List recent completed executions const executions = await client.listExecutions({ states: [3], // COMPLETED includeCompletedWithinMs: 10000, }); // Should find our execution const found = executions.find(e => e.executionId === executionId); expect(found).toBeDefined(); expect(found?.state).toBe(3); }); it('respects limit parameter', async () => { // Execute multiple scripts for (let i = 0; i < 5; i++) { const { executionId } = await client.executeAsync({ code: `console.log("Limit test ${i}");`, timeoutMs: 5000, }); await client.waitForExecution(executionId); } // List with limit const executions = await client.listExecutions({ limit: 3, includeCompletedWithinMs: 10000, }); expect(executions.length).toBeLessThanOrEqual(3); }); it('returns execution details', async () => { const { executionId } = await client.executeAsync({ code: 'console.log("Details test");', timeoutMs: 5000, }); await client.waitForExecution(executionId); const executions = await client.listExecutions({ includeCompletedWithinMs: 30000, // Longer window to ensure we catch it limit: 50, }); // The execution registry may clean up completed executions quickly, // so we verify the list call works and returns proper structure expect(Array.isArray(executions)).toBe(true); // If we find our execution, verify its structure const found = executions.find(e => e.executionId === executionId); if (found) { expect(found.startedAtMs).toBeGreaterThan(0); expect(found.codePreview).toContain('Details test'); expect(found.isCached).toBe(false); } }); }); describe('Execution Lifecycle', () => { it('tracks full execution lifecycle', async () => { // Start execution const { executionId, state: initialState } = await client.executeAsync({ code: ` console.log("Step 1"); await new Promise(r => setTimeout(r, 100)); console.log("Step 2"); `, timeoutMs: 10000, }); expect([1, 2]).toContain(initialState); // PENDING or RUNNING // Check while running await new Promise(r => setTimeout(r, 50)); const runningStatus = await client.getExecution(executionId); expect([2, 3]).toContain(runningStatus.state); // RUNNING or COMPLETED // Wait for completion const finalStatus = await client.waitForExecution(executionId); expect(finalStatus.state).toBe(3); // COMPLETED expect(finalStatus.output).toContain('Step 1'); expect(finalStatus.output).toContain('Step 2'); expect(finalStatus.result?.success).toBe(true); }); it('handles execution failure lifecycle', async () => { const { executionId } = await client.executeAsync({ code: ` console.log("Before error"); throw new Error("Test failure"); `, timeoutMs: 5000, }); const status = await client.waitForExecution(executionId); expect(status.state).toBe(4); // FAILED expect(status.output).toContain('Before error'); expect(status.result?.success).toBe(false); expect(status.result?.error).toContain('Test failure'); }); it('handles timeout lifecycle', async () => { const { executionId } = await client.executeAsync({ code: ` console.log("Starting long task"); await new Promise(r => setTimeout(r, 10000)); // 10 seconds `, timeoutMs: 200, // Very short timeout }); const status = await client.waitForExecution(executionId); expect(status.state).toBe(6); // TIMEOUT expect(status.output).toContain('Starting long task'); }); }); });

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