Skip to main content
Glama
runSandbox.test.tsβ€’67 kB
import { describe, expect, it, beforeAll, afterAll, afterEach } from 'vitest'; import { existsSync, mkdirSync, readdirSync, unlinkSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import type { Server } from '@grpc/grpc-js'; import { runSandboxTool } from '../tools/prodisco/runSandbox.js'; import { SCRIPTS_CACHE_DIR } from '../util/paths.js'; import { startServer } from '@prodisco/sandbox-server/server'; import { closeSandboxClient, getSandboxClient } from '@prodisco/sandbox-server/client'; // Test socket path to avoid conflicts const TEST_SOCKET_PATH = '/tmp/prodisco-sandbox-test.sock'; // Helper to execute runSandbox with proper typing const runSandbox = runSandboxTool.execute.bind(runSandboxTool) as (input: { mode?: 'execute' | 'stream' | 'async' | 'status' | 'cancel' | 'list' | 'test'; code?: string; cached?: string; timeout?: number; executionId?: string; wait?: boolean; outputOffset?: number; states?: Array<'pending' | 'running' | 'completed' | 'failed' | 'cancelled' | 'timeout'>; limit?: number; includeCompletedWithinMs?: number; tests?: string; }) => ReturnType<typeof runSandboxTool.execute>; // Test cached script for Cached Script Execution tests const testCachedScriptName = 'test-cached-script-for-runsandbox.ts'; const testCachedScriptPath = join(SCRIPTS_CACHE_DIR, testCachedScriptName); const testCachedScriptContent = `// Executed via sandbox at 2025-01-01T00:00:00.000Z // Test cached script console.log("executed from cache"); console.log("cached script working"); `; // gRPC server instance let grpcServer: Server; // Ensure cache directory exists, start gRPC server, and create test script before tests beforeAll(async () => { // Set environment for test socket process.env.SANDBOX_SOCKET_PATH = TEST_SOCKET_PATH; process.env.SCRIPTS_CACHE_DIR = SCRIPTS_CACHE_DIR; if (!existsSync(SCRIPTS_CACHE_DIR)) { mkdirSync(SCRIPTS_CACHE_DIR, { recursive: true }); } // Start the gRPC sandbox server grpcServer = await startServer({ socketPath: TEST_SOCKET_PATH, cacheDir: SCRIPTS_CACHE_DIR, }); // Wait for server to be healthy const client = getSandboxClient({ socketPath: TEST_SOCKET_PATH }); const healthy = await client.waitForHealthy(5000); if (!healthy) { throw new Error('Sandbox server failed to start'); } // Create test cached script writeFileSync(testCachedScriptPath, testCachedScriptContent); }); // Track scripts created during tests for cleanup const createdScripts: string[] = []; afterEach(() => { // Clean up any scripts created during tests for (const script of createdScripts) { const fullPath = join(SCRIPTS_CACHE_DIR, script); if (existsSync(fullPath)) { unlinkSync(fullPath); } } createdScripts.length = 0; }); afterAll(() => { // Close client and stop server closeSandboxClient(); if (grpcServer) { grpcServer.forceShutdown(); } // Clean up test cached script if (existsSync(testCachedScriptPath)) { unlinkSync(testCachedScriptPath); } // Final cleanup of any remaining test scripts if (existsSync(SCRIPTS_CACHE_DIR)) { const files = readdirSync(SCRIPTS_CACHE_DIR); for (const file of files) { if (file.startsWith('test-') || file.includes('-test-')) { const fullPath = join(SCRIPTS_CACHE_DIR, file); if (existsSync(fullPath)) { unlinkSync(fullPath); } } } } }); describe('prodisco.runSandbox', () => { describe('Basic Execution', () => { it('executes simple TypeScript code', async () => { const result = await runSandbox({ code: 'console.log("Hello, World!");', }); expect(result.success).toBe(true); expect(result.output).toContain('Hello, World!'); expect(result.executionTimeMs).toBeGreaterThan(0); }, 20000); it('captures console.log output', async () => { const result = await runSandbox({ code: ` console.log("line 1"); console.log("line 2"); console.log("line 3"); `, }); expect(result.success).toBe(true); expect(result.output).toContain('line 1'); expect(result.output).toContain('line 2'); expect(result.output).toContain('line 3'); }); it('captures console.error output with [ERROR] prefix', async () => { const result = await runSandbox({ code: 'console.error("This is an error");', }); expect(result.success).toBe(true); expect(result.output).toContain('[ERROR]'); expect(result.output).toContain('This is an error'); }); it('captures console.warn output with [WARN] prefix', async () => { const result = await runSandbox({ code: 'console.warn("This is a warning");', }); expect(result.success).toBe(true); expect(result.output).toContain('[WARN]'); expect(result.output).toContain('This is a warning'); }); it('captures console.info output with [INFO] prefix', async () => { const result = await runSandbox({ code: 'console.info("This is info");', }); expect(result.success).toBe(true); expect(result.output).toContain('[INFO]'); expect(result.output).toContain('This is info'); }); it('handles async/await code', async () => { const result = await runSandbox({ code: ` const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); await delay(10); console.log("async completed"); `, }); expect(result.success).toBe(true); expect(result.output).toContain('async completed'); }); it('handles TypeScript types', async () => { const result = await runSandbox({ code: ` interface Person { name: string; age: number; } const person: Person = { name: "Alice", age: 30 }; console.log(\`\${person.name} is \${person.age} years old\`); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Alice is 30 years old'); }); }); describe('Sandbox Environment', () => { it('allows requiring @kubernetes/client-node (if allowlisted)', async () => { const result = await runSandbox({ code: ` const k8s = require('@kubernetes/client-node'); console.log("k8s loaded:", typeof k8s !== 'undefined'); console.log("CoreV1Api:", typeof k8s.CoreV1Api); `, }); expect(result.success).toBe(true); expect(result.output).toContain('k8s loaded: true'); expect(result.output).toContain('CoreV1Api: function'); }); it('can construct KubeConfig via @kubernetes/client-node', async () => { const result = await runSandbox({ code: ` const k8s = require('@kubernetes/client-node'); const kc = new k8s.KubeConfig(); console.log("kc is KubeConfig:", kc.constructor.name); console.log("has loadFromOptions:", typeof kc.loadFromOptions); `, }); expect(result.success).toBe(true); expect(result.output).toContain('kc is KubeConfig: KubeConfig'); expect(result.output).toContain('has loadFromOptions: function'); }); it('provides require function for whitelisted modules', async () => { const result = await runSandbox({ code: ` const promClient = require('@prodisco/prometheus-client'); console.log("prometheus-client loaded:", typeof promClient !== 'undefined'); console.log("PrometheusClient:", typeof promClient.PrometheusClient); `, }); expect(result.success).toBe(true); expect(result.output).toContain('prometheus-client loaded: true'); expect(result.output).toContain('PrometheusClient: function'); }); it('require caches @kubernetes/client-node within an execution', async () => { const result = await runSandbox({ code: ` const k8s1 = require('@kubernetes/client-node'); const k8s2 = require('@kubernetes/client-node'); console.log("k8s require cached:", k8s1 === k8s2); `, }); expect(result.success).toBe(true); expect(result.output).toContain('k8s require cached: true'); }); it('throws error for non-whitelisted modules', async () => { const result = await runSandbox({ code: ` const fs = require('fs'); console.log(fs); `, }); expect(result.success).toBe(false); expect(result.error).toContain("Module 'fs' not available in sandbox"); }); it('provides process.env', async () => { const result = await runSandbox({ code: ` console.log("process.env available:", typeof process.env !== 'undefined'); console.log("PATH exists:", typeof process.env.PATH !== 'undefined'); `, }); expect(result.success).toBe(true); expect(result.output).toContain('process.env available: true'); }); it('provides standard globals (JSON, Date, Math, etc.)', async () => { const result = await runSandbox({ code: ` console.log("JSON:", typeof JSON); console.log("Date:", typeof Date); console.log("Math:", typeof Math); console.log("Promise:", typeof Promise); console.log("Buffer:", typeof Buffer); console.log("Array:", typeof Array); `, }); expect(result.success).toBe(true); expect(result.output).toContain('JSON: object'); expect(result.output).toContain('Date: function'); expect(result.output).toContain('Math: object'); expect(result.output).toContain('Promise: function'); expect(result.output).toContain('Buffer: function'); expect(result.output).toContain('Array: function'); }); it('provides setTimeout and setInterval', async () => { const result = await runSandbox({ code: ` console.log("setTimeout:", typeof setTimeout); console.log("setInterval:", typeof setInterval); console.log("clearTimeout:", typeof clearTimeout); console.log("clearInterval:", typeof clearInterval); `, }); expect(result.success).toBe(true); expect(result.output).toContain('setTimeout: function'); expect(result.output).toContain('setInterval: function'); expect(result.output).toContain('clearTimeout: function'); expect(result.output).toContain('clearInterval: function'); }); }); describe('Error Handling', () => { it('catches synchronous errors', async () => { const result = await runSandbox({ code: 'throw new Error("Intentional error");', }); expect(result.success).toBe(false); expect(result.error).toContain('Intentional error'); }); it('catches async errors', async () => { const result = await runSandbox({ code: ` await Promise.reject(new Error("Async error")); `, }); expect(result.success).toBe(false); expect(result.error).toContain('Async error'); }); it('catches syntax errors', async () => { const result = await runSandbox({ code: 'const x = {', // Invalid syntax }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('preserves output before error', async () => { const result = await runSandbox({ code: ` console.log("before error"); throw new Error("error occurred"); `, }); expect(result.success).toBe(false); expect(result.output).toContain('before error'); expect(result.error).toContain('error occurred'); }); it('handles undefined variable access', async () => { const result = await runSandbox({ code: 'console.log(undefinedVariable);', }); expect(result.success).toBe(false); expect(result.error).toContain('undefinedVariable'); }); }); describe('Timeout Handling', () => { it('respects default timeout', async () => { const result = await runSandbox({ code: ` // This should complete well within default timeout console.log("quick execution"); `, }); expect(result.success).toBe(true); expect(result.executionTimeMs).toBeLessThan(30000); }); it('respects custom timeout', async () => { const start = Date.now(); const result = await runSandbox({ code: ` await new Promise(resolve => setTimeout(resolve, 5000)); console.log("completed"); `, timeout: 100, // 100ms timeout }); const elapsed = Date.now() - start; expect(result.success).toBe(false); expect(result.error).toContain('timed out'); expect(elapsed).toBeLessThan(5000); // Should fail before 5 seconds }); it('completes before timeout for fast code', async () => { const result = await runSandbox({ code: ` console.log("fast"); `, timeout: 5000, }); expect(result.success).toBe(true); expect(result.executionTimeMs).toBeLessThan(5000); }); }); describe('Script Caching', () => { it('caches successfully executed scripts', async () => { const uniqueId = Date.now(); const result = await runSandbox({ code: ` // Test script ${uniqueId} for caching verification console.log("cached script test ${uniqueId}"); `, }); expect(result.success).toBe(true); // Wait for caching to complete await new Promise(resolve => setTimeout(resolve, 200)); // Check that a script was cached const files = readdirSync(SCRIPTS_CACHE_DIR); const _cachedScript = files.find(f => f.includes(uniqueId.toString().slice(-8))); // May or may not find by ID, but cache should have scripts expect(files.length).toBeGreaterThan(0); }, 30000); // Allow 30s for sandbox initialization it('does not cache failed scripts', async () => { const uniqueMarker = `FAIL_${Date.now()}`; const result = await runSandbox({ code: ` // Script that fails ${uniqueMarker} throw new Error("intentional failure"); `, }); expect(result.success).toBe(false); // Wait a moment for any potential caching to complete await new Promise(resolve => setTimeout(resolve, 200)); // Check that no script with our unique marker was cached // (avoid counting all files which can be flaky due to other tests) const files = readdirSync(SCRIPTS_CACHE_DIR, { withFileTypes: true }) .filter(f => f.isFile()) .map(f => f.name); const failedScriptCached = files.some(f => { const content = readFileSync(join(SCRIPTS_CACHE_DIR, f), 'utf-8'); return content.includes(uniqueMarker); }); expect(failedScriptCached).toBe(false); }); it('deduplicates scripts with identical content', async () => { // Use unique code to avoid collisions with other tests or runs const uniqueId = `${Date.now()}-${Math.random().toString(36).slice(2)}`; const uniqueMarker = `DEDUP_TEST_${uniqueId}`; const code = ` // Deduplication test script ${uniqueMarker} console.log("same content ${uniqueMarker}"); `; // Execute same code twice await runSandbox({ code }); await new Promise((resolve) => setTimeout(resolve, 200)); await runSandbox({ code }); await new Promise((resolve) => setTimeout(resolve, 200)); // Count files containing our unique marker - should be exactly 1 const filesWithMarker = readdirSync(SCRIPTS_CACHE_DIR) .filter((f) => f.endsWith('.ts')) .filter((f) => { const content = readFileSync(join(SCRIPTS_CACHE_DIR, f), 'utf-8'); return content.includes(uniqueMarker); }); expect(filesWithMarker.length).toBe(1); }); it('adds header comment to cached scripts', async () => { const uniqueContent = `console.log("header test ${Date.now()}");`; await runSandbox({ code: uniqueContent }); await new Promise(resolve => setTimeout(resolve, 200)); // Find the most recently created script const files = readdirSync(SCRIPTS_CACHE_DIR) .filter(f => f.endsWith('.ts')) .sort() .reverse(); if (files.length > 0) { const content = readFileSync(join(SCRIPTS_CACHE_DIR, files[0]), 'utf-8'); expect(content).toContain('// Executed via sandbox'); } }); }); describe('Cached Script Execution', () => { // Test script is created in the global beforeAll at the top of the file it('executes cached script by exact filename', async () => { const result = await runSandbox({ cached: testCachedScriptName, }); expect(result.success).toBe(true); expect(result.output).toContain('executed from cache'); expect(result.cachedScript).toBe(testCachedScriptName); }); it('executes cached script by filename without extension', async () => { const result = await runSandbox({ cached: testCachedScriptName.replace('.ts', ''), }); expect(result.success).toBe(true); expect(result.output).toContain('executed from cache'); }); it('executes cached script by partial match', async () => { const result = await runSandbox({ cached: 'cached-script-for-runsandbox', }); expect(result.success).toBe(true); expect(result.output).toContain('executed from cache'); }); it('returns error for non-existent cached script', async () => { const result = await runSandbox({ cached: 'nonexistent-script-xyz123.ts', }); expect(result.success).toBe(false); expect(result.error).toContain('not found'); }); it('strips header comment when executing cached script', async () => { const result = await runSandbox({ cached: testCachedScriptName, }); expect(result.success).toBe(true); // Should not have header comment in output (it's stripped before execution) expect(result.output).not.toContain('// Executed via sandbox'); }); it('does not re-cache already cached scripts', async () => { const beforeCount = readdirSync(SCRIPTS_CACHE_DIR).length; await runSandbox({ cached: testCachedScriptName }); await new Promise(resolve => setTimeout(resolve, 200)); const afterCount = readdirSync(SCRIPTS_CACHE_DIR).length; expect(afterCount).toBe(beforeCount); }); it('includes cachedScript field in result', async () => { const result = await runSandbox({ cached: testCachedScriptName, }); expect(result.cachedScript).toBe(testCachedScriptName); }); }); describe('Input Validation', () => { it('requires either code or cached parameter', async () => { const result = await runSandbox({}); expect(result.success).toBe(false); expect(result.error).toContain('code'); }); it('accepts code parameter', async () => { const result = await runSandbox({ code: 'console.log("test");', }); expect(result.success).toBe(true); }); it('accepts cached parameter', async () => { // This will fail because the script doesn't exist, but it validates the parameter const result = await runSandbox({ cached: 'some-script.ts', }); // Either success (if script exists) or specific "not found" error if (!result.success) { expect(result.error).toContain('not found'); } }); it('handles empty code string', async () => { const result = await runSandbox({ code: '', }); // Empty code may fail due to esbuild transformation // The important thing is it doesn't crash the system expect(result).toHaveProperty('mode', 'execute'); expect(result).toHaveProperty('success'); // If successful, should have executionTimeMs; if failed, may have just mode, success, error if (result.success) { expect(result).toHaveProperty('executionTimeMs'); } }); it('timeout must be positive', async () => { const result = await runSandbox({ code: 'console.log("test");', timeout: 1000, }); expect(result.success).toBe(true); }); }); describe('Result Structure', () => { it('returns complete result structure on success', async () => { const result = await runSandbox({ code: 'console.log("test");', }); expect(result).toHaveProperty('mode', 'execute'); expect(result).toHaveProperty('success', true); expect(result).toHaveProperty('output'); expect(result).toHaveProperty('executionTimeMs'); expect(typeof result.output).toBe('string'); expect(typeof result.executionTimeMs).toBe('number'); }); it('returns complete result structure on failure', async () => { const result = await runSandbox({ code: 'throw new Error("test error");', }); expect(result).toHaveProperty('mode', 'execute'); expect(result).toHaveProperty('success', false); expect(result).toHaveProperty('output'); expect(result).toHaveProperty('error'); expect(result).toHaveProperty('executionTimeMs'); expect(typeof result.error).toBe('string'); }); it('execution time is reasonable', async () => { const result = await runSandbox({ code: 'console.log("quick");', }); expect(result.executionTimeMs).toBeGreaterThanOrEqual(0); expect(result.executionTimeMs).toBeLessThan(10000); // Should complete in under 10s }); }); describe('TypeScript Features', () => { it('handles arrow functions', async () => { const result = await runSandbox({ code: ` const add = (a: number, b: number): number => a + b; console.log("Result:", add(2, 3)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Result: 5'); }); it('handles async arrow functions', async () => { const result = await runSandbox({ code: ` const asyncFn = async (): Promise<string> => { return "async result"; }; const value = await asyncFn(); console.log(value); `, }); expect(result.success).toBe(true); expect(result.output).toContain('async result'); }); it('handles destructuring', async () => { const result = await runSandbox({ code: ` const obj = { a: 1, b: 2, c: 3 }; const { a, ...rest } = obj; console.log("a:", a); console.log("rest:", JSON.stringify(rest)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('a: 1'); expect(result.output).toContain('rest: {"b":2,"c":3}'); }); it('handles template literals', async () => { const result = await runSandbox({ code: ` const name = "World"; const greeting = \`Hello, \${name}!\`; console.log(greeting); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Hello, World!'); }); it('handles class definitions', async () => { const result = await runSandbox({ code: ` class Greeter { private name: string; constructor(name: string) { this.name = name; } greet(): string { return \`Hello, \${this.name}\`; } } const g = new Greeter("TypeScript"); console.log(g.greet()); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Hello, TypeScript'); }); it('handles optional chaining', async () => { const result = await runSandbox({ code: ` const obj: { a?: { b?: { c: number } } } = {}; console.log("value:", obj?.a?.b?.c ?? "undefined"); `, }); expect(result.success).toBe(true); expect(result.output).toContain('value: undefined'); }); it('handles nullish coalescing', async () => { const result = await runSandbox({ code: ` const value: string | null = null; console.log("result:", value ?? "default"); `, }); expect(result.success).toBe(true); expect(result.output).toContain('result: default'); }); }); describe('Kubernetes Integration', () => { it('can create API client from a minimally configured KubeConfig', async () => { const result = await runSandbox({ code: ` const k8s = require('@kubernetes/client-node'); const kc = new k8s.KubeConfig(); kc.loadFromOptions({ clusters: [{ name: 'cluster', server: 'https://example.invalid' }], users: [{ name: 'user' }], contexts: [{ name: 'context', user: 'user', cluster: 'cluster' }], currentContext: 'context', }); const api = kc.makeApiClient(k8s.CoreV1Api); console.log("API client created:", api.constructor.name); `, }); expect(result.success).toBe(true); // Constructor name may include "Object" prefix in some environments expect(result.output).toContain('API client created:'); expect(result.output).toContain('CoreV1Api'); }); it('can access different API classes', async () => { const result = await runSandbox({ code: ` const k8s = require('@kubernetes/client-node'); console.log("CoreV1Api:", typeof k8s.CoreV1Api); console.log("AppsV1Api:", typeof k8s.AppsV1Api); console.log("BatchV1Api:", typeof k8s.BatchV1Api); console.log("NetworkingV1Api:", typeof k8s.NetworkingV1Api); `, }); expect(result.success).toBe(true); expect(result.output).toContain('CoreV1Api: function'); expect(result.output).toContain('AppsV1Api: function'); expect(result.output).toContain('BatchV1Api: function'); expect(result.output).toContain('NetworkingV1Api: function'); }); }); describe('Prometheus Integration', () => { it('can load prometheus-client module', async () => { const result = await runSandbox({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); console.log("PrometheusClient loaded:", typeof PrometheusClient === 'function'); `, }); expect(result.success).toBe(true); expect(result.output).toContain('PrometheusClient loaded: true'); }); it('can create PrometheusClient instance', async () => { const result = await runSandbox({ code: ` const { PrometheusClient } = require('@prodisco/prometheus-client'); const client = new PrometheusClient({ endpoint: 'http://localhost:9090' }); console.log("Client created:", client.constructor.name); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Client created: PrometheusClient'); }); it('can access PROMETHEUS_URL from environment', async () => { const result = await runSandbox({ code: ` const url = process.env.PROMETHEUS_URL; console.log("PROMETHEUS_URL type:", typeof url); `, }); expect(result.success).toBe(true); expect(result.output).toContain('PROMETHEUS_URL type:'); }); }); describe('Tool Definition', () => { it('has correct name', () => { expect(runSandboxTool.name).toBe('prodisco.runSandbox'); }); it('has description', () => { expect(runSandboxTool.description).toBeDefined(); expect(runSandboxTool.description.length).toBeGreaterThan(0); }); it('description mentions key features', () => { expect(runSandboxTool.description).toContain('TypeScript'); expect(runSandboxTool.description).toContain('sandbox'); expect(runSandboxTool.description).toContain('Kubernetes'); expect(runSandboxTool.description).toContain('Prometheus'); }); it('has schema', () => { expect(runSandboxTool.schema).toBeDefined(); }); }); describe('Edge Cases', () => { it('handles very long output', async () => { const result = await runSandbox({ code: ` for (let i = 0; i < 1000; i++) { console.log("Line " + i); } `, }); expect(result.success).toBe(true); expect(result.output).toContain('Line 0'); expect(result.output).toContain('Line 999'); }); it('handles Unicode characters', async () => { const result = await runSandbox({ code: ` console.log("Hello δΈ–η•Œ! 🌍"); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Hello δΈ–η•Œ! 🌍'); }); it('handles multiline strings', async () => { const result = await runSandbox({ code: ` const multiline = \` Line 1 Line 2 Line 3 \`; console.log(multiline); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Line 1'); expect(result.output).toContain('Line 2'); expect(result.output).toContain('Line 3'); }); it('handles JSON stringify', async () => { const result = await runSandbox({ code: ` const obj = { name: "test", values: [1, 2, 3] }; console.log(JSON.stringify(obj, null, 2)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('"name": "test"'); }); it('handles Buffer operations', async () => { const result = await runSandbox({ code: ` const buf = Buffer.from("hello"); console.log("Buffer length:", buf.length); console.log("Buffer toString:", buf.toString()); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Buffer length: 5'); expect(result.output).toContain('Buffer toString: hello'); }); it('handles Date operations', async () => { const result = await runSandbox({ code: ` const now = new Date(); console.log("Date type:", typeof now.getTime()); console.log("Is valid:", !isNaN(now.getTime())); `, }); expect(result.success).toBe(true); expect(result.output).toContain('Date type: number'); expect(result.output).toContain('Is valid: true'); }); it('handles Math operations', async () => { const result = await runSandbox({ code: ` console.log("PI:", Math.PI.toFixed(4)); console.log("sqrt(16):", Math.sqrt(16)); console.log("max(1,5,3):", Math.max(1, 5, 3)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('PI: 3.1416'); expect(result.output).toContain('sqrt(16): 4'); expect(result.output).toContain('max(1,5,3): 5'); }); }); }); describe('runSandbox - Mutex and Concurrency', () => { it('handles concurrent executions', async () => { const results = await Promise.all([ runSandbox({ code: 'console.log("exec 1");' }), runSandbox({ code: 'console.log("exec 2");' }), runSandbox({ code: 'console.log("exec 3");' }), ]); expect(results[0].success).toBe(true); expect(results[1].success).toBe(true); expect(results[2].success).toBe(true); expect(results[0].output).toContain('exec 1'); expect(results[1].output).toContain('exec 2'); expect(results[2].output).toContain('exec 3'); }); it('handles concurrent caching without duplicates', async () => { const beforeCount = readdirSync(SCRIPTS_CACHE_DIR).length; const uniqueCode = `console.log("concurrent ${Date.now()}");`; // Execute same code concurrently await Promise.all([ runSandbox({ code: uniqueCode }), runSandbox({ code: uniqueCode }), runSandbox({ code: uniqueCode }), ]); await new Promise(resolve => setTimeout(resolve, 500)); const afterCount = readdirSync(SCRIPTS_CACHE_DIR).length; // Should only add one script despite concurrent executions expect(afterCount - beforeCount).toBeLessThanOrEqual(1); }); }); // ============================================================================= // Mode-Based Execution Tests // ============================================================================= describe('runSandbox - Execute Mode', () => { it('defaults to execute mode when mode is not specified', async () => { const result = await runSandbox({ code: 'console.log("default mode");', }); expect(result.mode).toBe('execute'); expect(result.success).toBe(true); expect(result.output).toContain('default mode'); }); it('explicitly uses execute mode', async () => { const result = await runSandbox({ mode: 'execute', code: 'console.log("explicit execute");', }); expect(result.mode).toBe('execute'); expect(result.success).toBe(true); expect(result.output).toContain('explicit execute'); }); it('returns executionTimeMs in execute mode', async () => { const result = await runSandbox({ mode: 'execute', code: 'console.log("timing test");', }); expect(result.mode).toBe('execute'); expect(result).toHaveProperty('executionTimeMs'); expect(typeof result.executionTimeMs).toBe('number'); expect(result.executionTimeMs).toBeGreaterThanOrEqual(0); }); it('returns error when neither code nor cached provided in execute mode', async () => { const result = await runSandbox({ mode: 'execute', }); expect(result.mode).toBe('execute'); expect(result.success).toBe(false); expect(result.error).toContain('Either "code" or "cached" must be provided'); }); }); describe('runSandbox - Stream Mode', () => { it('executes code in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: 'console.log("stream test");', }); expect(result.mode).toBe('stream'); expect(result.success).toBe(true); expect(result.output).toContain('stream test'); }); it('returns executionId in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: 'console.log("stream id test");', }); expect(result.mode).toBe('stream'); expect(result).toHaveProperty('executionId'); expect(typeof result.executionId).toBe('string'); expect(result.executionId.length).toBeGreaterThan(0); }); it('returns state in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: 'console.log("state test");', }); expect(result.mode).toBe('stream'); expect(result).toHaveProperty('state'); expect(result.state).toBe('completed'); }); it('captures error output separately in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: 'console.error("error message"); console.log("normal message");', }); expect(result.mode).toBe('stream'); expect(result.success).toBe(true); expect(result.output).toContain('normal message'); expect(result).toHaveProperty('errorOutput'); }); it('handles multi-line output in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: ` for (let i = 0; i < 5; i++) { console.log("Line " + i); } `, }); expect(result.mode).toBe('stream'); expect(result.success).toBe(true); expect(result.output).toContain('Line 0'); expect(result.output).toContain('Line 4'); }); it('returns error when neither code nor cached provided in stream mode', async () => { const result = await runSandbox({ mode: 'stream', }); expect(result.mode).toBe('stream'); expect(result.success).toBe(false); expect(result.error).toContain('Either "code" or "cached" must be provided'); }); it('handles execution failure in stream mode', async () => { const result = await runSandbox({ mode: 'stream', code: 'throw new Error("stream error");', }); expect(result.mode).toBe('stream'); expect(result.success).toBe(false); expect(result.state).toBe('failed'); }); }); describe('runSandbox - Async Mode', () => { it('starts async execution and returns execution ID', async () => { const result = await runSandbox({ mode: 'async', code: 'console.log("async test");', }); expect(result.mode).toBe('async'); expect(result).toHaveProperty('executionId'); expect(typeof result.executionId).toBe('string'); expect(result.executionId.length).toBeGreaterThan(0); }); it('returns initial state in async mode', async () => { const result = await runSandbox({ mode: 'async', code: 'console.log("async state test");', }); expect(result.mode).toBe('async'); expect(result).toHaveProperty('state'); // Initial state should be pending or running expect(['pending', 'running', 'completed']).toContain(result.state); }); it('returns helpful message in async mode', async () => { const result = await runSandbox({ mode: 'async', code: 'console.log("async message test");', }); expect(result.mode).toBe('async'); expect(result).toHaveProperty('message'); expect(result.message).toContain('status'); expect(result.message).toContain(result.executionId); }); it('returns error when neither code nor cached provided in async mode', async () => { const result = await runSandbox({ mode: 'async', }); expect(result.mode).toBe('async'); expect(result.success).toBe(false); expect(result.error).toContain('Either "code" or "cached" must be provided'); }); }); describe('runSandbox - Status Mode', () => { it('gets status of an async execution', async () => { // First start an async execution const asyncResult = await runSandbox({ mode: 'async', code: 'console.log("status check");', }); expect(asyncResult.mode).toBe('async'); const executionId = asyncResult.executionId; // Wait a bit for execution to complete await new Promise(resolve => setTimeout(resolve, 200)); // Then check status const statusResult = await runSandbox({ mode: 'status', executionId, }); expect(statusResult.mode).toBe('status'); expect(statusResult.executionId).toBe(executionId); expect(statusResult).toHaveProperty('state'); expect(statusResult).toHaveProperty('output'); }); it('waits for completion when wait=true', async () => { // Start a longer running async execution const asyncResult = await runSandbox({ mode: 'async', code: ` await new Promise(r => setTimeout(r, 100)); console.log("waited execution"); `, }); expect(asyncResult.mode).toBe('async'); const executionId = asyncResult.executionId; // Check status with wait const statusResult = await runSandbox({ mode: 'status', executionId, wait: true, }); expect(statusResult.mode).toBe('status'); expect(statusResult.state).toBe('completed'); expect(statusResult.output).toContain('waited execution'); }); it('supports incremental output reading with outputOffset', async () => { // Start async execution with multiple outputs const asyncResult = await runSandbox({ mode: 'async', code: ` console.log("line1"); console.log("line2"); console.log("line3"); `, }); const executionId = asyncResult.executionId; // Wait for completion await new Promise(resolve => setTimeout(resolve, 200)); // First read const status1 = await runSandbox({ mode: 'status', executionId, }); expect(status1.mode).toBe('status'); expect(status1).toHaveProperty('outputLength'); expect(status1.outputLength).toBeGreaterThan(0); // Second read with offset (should return less or empty) const status2 = await runSandbox({ mode: 'status', executionId, outputOffset: status1.outputLength, }); expect(status2.mode).toBe('status'); // Output should be empty or minimal since we're reading from the end expect(status2.output.length).toBeLessThanOrEqual(status1.output.length); }); it('returns result details when execution is complete', async () => { const asyncResult = await runSandbox({ mode: 'async', code: 'console.log("complete test");', }); const executionId = asyncResult.executionId; // Wait and get final status const statusResult = await runSandbox({ mode: 'status', executionId, wait: true, }); expect(statusResult.mode).toBe('status'); expect(statusResult.state).toBe('completed'); expect(statusResult).toHaveProperty('result'); expect(statusResult.result).toHaveProperty('success', true); expect(statusResult.result).toHaveProperty('executionTimeMs'); }); it('returns error when executionId is not provided', async () => { const result = await runSandbox({ mode: 'status', }); expect(result.mode).toBe('status'); expect(result.success).toBe(false); expect(result.error).toContain('executionId is required'); }); it('handles non-existent execution ID', async () => { const result = await runSandbox({ mode: 'status', executionId: 'non-existent-id-12345', }); expect(result.mode).toBe('status'); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); describe('runSandbox - Cancel Mode', () => { it('cancels a running execution', async () => { // Start a long-running async execution const asyncResult = await runSandbox({ mode: 'async', code: ` for (let i = 0; i < 100; i++) { await new Promise(r => setTimeout(r, 100)); console.log("iteration", i); } `, }); expect(asyncResult.mode).toBe('async'); const executionId = asyncResult.executionId; // Small delay to ensure execution starts await new Promise(resolve => setTimeout(resolve, 50)); // Cancel the execution const cancelResult = await runSandbox({ mode: 'cancel', executionId, }); expect(cancelResult.mode).toBe('cancel'); expect(cancelResult.executionId).toBe(executionId); expect(cancelResult).toHaveProperty('state'); // State should be cancelled or already completed expect(['cancelled', 'completed', 'failed']).toContain(cancelResult.state); }); it('returns error when executionId is not provided', async () => { const result = await runSandbox({ mode: 'cancel', }); expect(result.mode).toBe('cancel'); expect(result.success).toBe(false); expect(result.error).toContain('executionId is required'); }); it('handles cancellation of already completed execution', async () => { // Start and wait for a quick execution const asyncResult = await runSandbox({ mode: 'async', code: 'console.log("quick");', }); const executionId = asyncResult.executionId; // Wait for completion await new Promise(resolve => setTimeout(resolve, 200)); // Try to cancel completed execution const cancelResult = await runSandbox({ mode: 'cancel', executionId, }); expect(cancelResult.mode).toBe('cancel'); expect(cancelResult.executionId).toBe(executionId); // Should indicate it's already completed expect(['completed', 'cancelled', 'failed']).toContain(cancelResult.state); }); }); describe('runSandbox - List Mode', () => { it('lists recent executions', async () => { // Execute a few scripts first await runSandbox({ code: 'console.log("list test 1");' }); await runSandbox({ code: 'console.log("list test 2");' }); const result = await runSandbox({ mode: 'list', includeCompletedWithinMs: 10000, // Last 10 seconds }); expect(result.mode).toBe('list'); expect(result).toHaveProperty('executions'); expect(Array.isArray(result.executions)).toBe(true); expect(result).toHaveProperty('totalCount'); }); it('filters by execution state', async () => { const result = await runSandbox({ mode: 'list', states: ['completed'], includeCompletedWithinMs: 10000, }); expect(result.mode).toBe('list'); expect(result).toHaveProperty('executions'); // All returned executions should be completed for (const exec of result.executions) { expect(exec.state).toBe('completed'); } }); it('respects limit parameter', async () => { // Execute several scripts for (let i = 0; i < 5; i++) { await runSandbox({ code: `console.log("limit test ${i}");` }); } const result = await runSandbox({ mode: 'list', limit: 3, includeCompletedWithinMs: 10000, }); expect(result.mode).toBe('list'); expect(result.executions.length).toBeLessThanOrEqual(3); }); it('returns execution details', async () => { await runSandbox({ code: 'console.log("details test");' }); const result = await runSandbox({ mode: 'list', includeCompletedWithinMs: 5000, limit: 5, }); expect(result.mode).toBe('list'); if (result.executions.length > 0) { const exec = result.executions[0]; expect(exec).toHaveProperty('executionId'); expect(exec).toHaveProperty('state'); expect(exec).toHaveProperty('startedAtMs'); expect(exec).toHaveProperty('codePreview'); expect(exec).toHaveProperty('isCached'); } }); it('can list running executions', async () => { // Start a long-running async execution const asyncResult = await runSandbox({ mode: 'async', code: ` for (let i = 0; i < 10; i++) { await new Promise(r => setTimeout(r, 500)); console.log(i); } `, }); // Small delay await new Promise(resolve => setTimeout(resolve, 50)); // List running executions const listResult = await runSandbox({ mode: 'list', states: ['running'], }); expect(listResult.mode).toBe('list'); // May or may not find running execution depending on timing expect(listResult).toHaveProperty('executions'); // Cancel the long-running execution await runSandbox({ mode: 'cancel', executionId: asyncResult.executionId, }); }); it('returns empty list when no matching executions', async () => { const result = await runSandbox({ mode: 'list', states: ['pending'], // Unlikely to have pending executions limit: 10, }); expect(result.mode).toBe('list'); expect(result.executions).toEqual([]); expect(result.totalCount).toBe(0); }); }); describe('runSandbox - Mode Integration', () => { it('can execute async, check status, and verify completion', async () => { // Step 1: Start async execution const asyncResult = await runSandbox({ mode: 'async', code: 'console.log("integration test");', }); expect(asyncResult.mode).toBe('async'); const executionId = asyncResult.executionId; // Step 2: Wait for completion using status mode const statusResult = await runSandbox({ mode: 'status', executionId, wait: true, }); expect(statusResult.mode).toBe('status'); expect(statusResult.state).toBe('completed'); expect(statusResult.output).toContain('integration test'); // Step 3: Verify list mode works (may or may not contain the specific execution // depending on cleanup timing, so just verify the list call works) const listResult = await runSandbox({ mode: 'list', includeCompletedWithinMs: 30000, // Longer window limit: 50, }); expect(listResult.mode).toBe('list'); expect(Array.isArray(listResult.executions)).toBe(true); // The execution registry may have already cleaned up, so we just verify the list works }); it('stream and execute modes produce consistent results', async () => { const code = 'console.log("consistency test"); console.log(1 + 2);'; const executeResult = await runSandbox({ mode: 'execute', code, }); const streamResult = await runSandbox({ mode: 'stream', code, }); // Both should succeed expect(executeResult.success).toBe(true); expect(streamResult.success).toBe(true); // Both should have the same output content expect(executeResult.output).toContain('consistency test'); expect(streamResult.output).toContain('consistency test'); expect(executeResult.output).toContain('3'); expect(streamResult.output).toContain('3'); }); it('handles concurrent async executions', async () => { // Start multiple async executions const [async1, async2, async3] = await Promise.all([ runSandbox({ mode: 'async', code: 'console.log("concurrent 1");' }), runSandbox({ mode: 'async', code: 'console.log("concurrent 2");' }), runSandbox({ mode: 'async', code: 'console.log("concurrent 3");' }), ]); // All should have unique execution IDs expect(async1.executionId).not.toBe(async2.executionId); expect(async2.executionId).not.toBe(async3.executionId); expect(async1.executionId).not.toBe(async3.executionId); // Wait for all to complete const [status1, status2, status3] = await Promise.all([ runSandbox({ mode: 'status', executionId: async1.executionId, wait: true }), runSandbox({ mode: 'status', executionId: async2.executionId, wait: true }), runSandbox({ mode: 'status', executionId: async3.executionId, wait: true }), ]); // All should complete successfully expect(status1.state).toBe('completed'); expect(status2.state).toBe('completed'); expect(status3.state).toBe('completed'); }); it('cached scripts work in all execution modes', async () => { // Test execute mode with cached script const executeResult = await runSandbox({ mode: 'execute', cached: testCachedScriptName, }); expect(executeResult.mode).toBe('execute'); expect(executeResult.success).toBe(true); expect(executeResult.output).toContain('executed from cache'); // Test stream mode with cached script const streamResult = await runSandbox({ mode: 'stream', cached: testCachedScriptName, }); expect(streamResult.mode).toBe('stream'); expect(streamResult.success).toBe(true); expect(streamResult.output).toContain('executed from cache'); // Test async mode with cached script const asyncResult = await runSandbox({ mode: 'async', cached: testCachedScriptName, }); expect(asyncResult.mode).toBe('async'); expect(asyncResult.executionId).toBeDefined(); // Verify async completed successfully const statusResult = await runSandbox({ mode: 'status', executionId: asyncResult.executionId, wait: true, }); expect(statusResult.state).toBe('completed'); expect(statusResult.output).toContain('executed from cache'); }); }); // ============================================================================ // Analytics Libraries Integration Tests // ============================================================================ describe('prodisco.runSandbox - Analytics Libraries Integration', () => { describe('simple-statistics Integration', () => { it('can require and use simple-statistics', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); console.log('mean:', ss.mean([1, 2, 3, 4, 5])); console.log('median:', ss.median([1, 2, 3, 4, 5])); console.log('sum:', ss.sum([1, 2, 3, 4, 5])); `, }); expect(result.success).toBe(true); expect(result.output).toContain('mean: 3'); expect(result.output).toContain('median: 3'); expect(result.output).toContain('sum: 15'); }); it('calculates standard deviation and variance', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); const data = [2, 4, 4, 4, 5, 5, 7, 9]; console.log('variance:', ss.variance(data)); console.log('std:', ss.standardDeviation(data).toFixed(2)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('variance: 4'); expect(result.output).toContain('std: 2.00'); }); it('performs linear regression analysis', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); const data = [[0, 0], [1, 2], [2, 4], [3, 6], [4, 8]]; const regression = ss.linearRegression(data); console.log('slope:', regression.m); console.log('intercept:', regression.b); const line = ss.linearRegressionLine(regression); console.log('predict(5):', line(5)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('slope: 2'); expect(result.output).toContain('intercept: 0'); expect(result.output).toContain('predict(5): 10'); }); it('calculates correlation coefficient', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); const x = [1, 2, 3, 4, 5]; const y = [2, 4, 6, 8, 10]; // perfectly correlated // Round to avoid floating point precision issues console.log('correlation:', ss.sampleCorrelation(x, y).toFixed(4)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('correlation: 1.0000'); }); it('calculates quantiles and percentiles', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; console.log('p25:', ss.quantile(data, 0.25)); console.log('p50:', ss.quantile(data, 0.50)); console.log('p75:', ss.quantile(data, 0.75)); `, }); expect(result.success).toBe(true); expect(result.output).toContain('p50:'); }); }); describe('Analytics Error Handling', () => { it('handles empty array in simple-statistics', async () => { const result = await runSandbox({ code: ` const ss = require('simple-statistics'); try { ss.mean([]); } catch (e) { console.log('caught:', e.message); } `, }); expect(result.success).toBe(true); expect(result.output).toContain('caught:'); }); }); describe('Analytics with Streaming Mode', () => { it('streams output from analytics computations', async () => { const result = await runSandbox({ mode: 'stream', code: ` const ss = require('simple-statistics'); console.log('step1: loading data'); const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; console.log('step2: computing mean'); console.log('mean:', ss.mean(data)); console.log('step3: computing std'); console.log('std:', ss.standardDeviation(data).toFixed(2)); console.log('step4: done'); `, }); expect(result.success).toBe(true); expect(result.output).toContain('step1:'); expect(result.output).toContain('step2:'); expect(result.output).toContain('step3:'); expect(result.output).toContain('step4:'); expect(result.output).toContain('mean: 5.5'); }); }); describe('Analytics with Async Mode', () => { it('executes analytics computation asynchronously', async () => { // Start async execution const asyncResult = await runSandbox({ mode: 'async', code: ` const ss = require('simple-statistics'); // Simulate some computation const data = Array.from({ length: 100 }, (_, i) => i + 1); console.log('mean:', ss.mean(data)); console.log('median:', ss.median(data)); console.log('done'); `, }); expect(asyncResult.mode).toBe('async'); expect(asyncResult.executionId).toBeDefined(); // Wait for completion const statusResult = await runSandbox({ mode: 'status', executionId: asyncResult.executionId, wait: true, }); expect(statusResult.state).toBe('completed'); expect(statusResult.output).toContain('mean: 50.5'); expect(statusResult.output).toContain('median: 50.5'); expect(statusResult.output).toContain('done'); }); }); }); // ============================================================================ // Test Mode Tests // ============================================================================ describe('runSandbox - Test Mode', () => { describe('Basic Test Execution', () => { it('runs passing tests and returns success', async () => { const result = await runSandbox({ mode: 'test', code: ` function add(a: number, b: number): number { return a + b; } `, tests: ` test("adds positive numbers", () => { assert.is(add(1, 2), 3); }); test("adds negative numbers", () => { assert.is(add(-1, -2), -3); }); `, }); expect(result.mode).toBe('test'); expect(result.success).toBe(true); expect(result.summary).toEqual({ total: 2, passed: 2, failed: 0, skipped: 0, }); expect(result.tests).toHaveLength(2); expect(result.tests[0].name).toBe('adds positive numbers'); expect(result.tests[0].passed).toBe(true); expect(result.tests[1].name).toBe('adds negative numbers'); expect(result.tests[1].passed).toBe(true); }); it('runs failing tests and returns failure', async () => { const result = await runSandbox({ mode: 'test', code: ` function multiply(a: number, b: number): number { return a + b; // Bug: should be a * b } `, tests: ` test("multiplies numbers", () => { assert.is(multiply(2, 3), 6); }); `, }); expect(result.mode).toBe('test'); expect(result.success).toBe(false); expect(result.summary.total).toBe(1); expect(result.summary.passed).toBe(0); expect(result.summary.failed).toBe(1); expect(result.tests[0].passed).toBe(false); expect(result.tests[0].error).toBeDefined(); }); it('handles mixed passing and failing tests', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("passing test", () => { assert.is(1 + 1, 2); }); test("failing test", () => { assert.is(1 + 1, 3); }); test("another passing test", () => { assert.ok(true); }); `, }); expect(result.mode).toBe('test'); expect(result.success).toBe(false); expect(result.summary).toEqual({ total: 3, passed: 2, failed: 1, skipped: 0, }); }); }); describe('Test Assertions', () => { it('supports assert.is for strict equality', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("assert.is works", () => { assert.is(1, 1); assert.is("hello", "hello"); }); `, }); expect(result.success).toBe(true); }); it('supports assert.equal for deep equality', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("assert.equal works", () => { assert.equal({ a: 1 }, { a: 1 }); assert.equal([1, 2, 3], [1, 2, 3]); }); `, }); expect(result.success).toBe(true); }); it('supports assert.ok for truthy values', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("assert.ok works", () => { assert.ok(true); assert.ok(1); assert.ok("truthy"); }); `, }); expect(result.success).toBe(true); }); it('supports assert.not for falsy values', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("assert.not works", () => { assert.not(false); assert.not(0); assert.not(""); }); `, }); expect(result.success).toBe(true); }); it('supports assert.throws for error checking', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("assert.throws works", () => { assert.throws(() => { throw new Error("expected"); }); }); `, }); expect(result.success).toBe(true); }); }); describe('Async Tests', () => { it('handles async test functions', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("async test with Promise.resolve", async () => { const value = await Promise.resolve(42); assert.is(value, 42); }); `, }); expect(result.success).toBe(true); expect(result.tests[0].passed).toBe(true); }); it('handles async tests with setTimeout', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("async test with delay", async () => { await new Promise(r => setTimeout(r, 10)); assert.ok(true); }); `, }); expect(result.success).toBe(true); }); }); describe('Error Handling', () => { it('returns error when tests parameter is missing', async () => { const result = await runSandbox({ mode: 'test', }); expect(result.mode).toBe('test'); expect(result.success).toBe(false); expect(result.error).toContain('tests'); }); it('returns error when tests parameter is empty', async () => { const result = await runSandbox({ mode: 'test', tests: '', }); expect(result.mode).toBe('test'); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('handles syntax errors in test code', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("broken", () => { const x = { }); `, }); expect(result.mode).toBe('test'); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it('handles runtime errors in tests', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("runtime error", () => { const obj: any = null; obj.foo.bar; }); `, }); expect(result.success).toBe(false); expect(result.tests[0].passed).toBe(false); expect(result.tests[0].error).toBeDefined(); }); }); describe('Test Result Structure', () => { it('includes execution time for each test', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("test with measurable duration", () => { let sum = 0; for (let i = 0; i < 10000; i++) sum += i; assert.ok(true); }); `, }); expect(result.success).toBe(true); expect(result.tests[0].durationMs).toBeGreaterThanOrEqual(0); }); it('includes overall execution time', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("quick test", () => { assert.ok(true); }); `, }); expect(result.executionTimeMs).toBeGreaterThanOrEqual(0); }); it('returns proper mode identifier', async () => { const result = await runSandbox({ mode: 'test', tests: ` test("simple", () => { assert.ok(true); }); `, }); expect(result.mode).toBe('test'); }); }); describe('Implementation Code Separation', () => { it('tests implementation provided in code parameter', async () => { const result = await runSandbox({ mode: 'test', code: ` function fibonacci(n: number): number[] { if (n <= 0) return []; if (n === 1) return [0]; const seq = [0, 1]; for (let i = 2; i < n; i++) { seq.push(seq[i-1] + seq[i-2]); } return seq; } `, tests: ` test("fibonacci(5) returns correct sequence", () => { assert.equal(fibonacci(5), [0, 1, 1, 2, 3]); }); test("fibonacci(0) returns empty array", () => { assert.equal(fibonacci(0), []); }); `, }); expect(result.success).toBe(true); expect(result.summary.passed).toBe(2); }); it('can define functions inline in tests without code parameter', async () => { const result = await runSandbox({ mode: 'test', tests: ` function double(x: number) { return x * 2; } test("double works", () => { assert.is(double(5), 10); }); `, }); expect(result.success).toBe(true); }); }); });

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