import { test as base, onTestFailed } from 'vitest' import { BuildInfo, LogEntry, Sandbox, SandboxOpts, Template, TemplateClass, Volume, } from '../src' import { template } from './template' interface SandboxFixture { sandbox: Sandbox template: string sandboxTestId: string sandboxOpts: Partial } interface VolumeFixture { volume: Volume } interface BuildTemplateFixture { buildTemplate: ( template: TemplateClass, options?: { name?: string; skipCache?: boolean }, onBuildLogs?: (logEntry: LogEntry) => void ) => Promise } async function buildTemplate( template: TemplateClass, options?: { name?: string; skipCache?: boolean }, onBuildLogs?: (logEntry: LogEntry) => void ): Promise { const buildName = options?.name || `e2b-test-${generateRandomString()}` const buildInfo: { templateId?: string; buildId?: string } = {} const captureLogs = (log: LogEntry) => { if (log.message.includes('Template created with ID:')) { const match = log.message.match( /Template created with ID: ([^,]+), Build ID: (.+)/ ) if (match) { buildInfo.templateId = match[1] buildInfo.buildId = match[2] } } onBuildLogs?.(log) } try { return await Template.build(template, buildName, { cpuCount: 1, memoryMB: 1024, skipCache: options?.skipCache, onBuildLogs: captureLogs, }) } catch (e) { console.error( `\n[BUILD FAILED] name=${buildName}, ` + `template_id=${buildInfo.templateId}, ` + `build_id=${buildInfo.buildId}, error=${e}` ) throw e } } export const sandboxTest = base.extend({ template, sandboxTestId: [ // eslint-disable-next-line no-empty-pattern async ({}, use) => { const id = `test-${generateRandomString()}` await use(id) }, { auto: true }, ], sandboxOpts: {}, sandbox: [ async ({ sandboxTestId, sandboxOpts }, use) => { const sandbox = await Sandbox.create(template, { metadata: { sandboxTestId }, ...sandboxOpts, }) onTestFailed(() => { console.error(`\n[TEST FAILED] Sandbox ID: ${sandbox.sandboxId}`) }) try { await use(sandbox) } finally { try { await sandbox.kill() } catch (err) { if (!isDebug) { console.warn( 'Failed to kill sandbox — this is expected if the test runs with local envd.' ) } } } }, { auto: false }, ], }) export const buildTemplateTest = base.extend({ buildTemplate: [ // eslint-disable-next-line no-empty-pattern async ({}, use) => { await use(buildTemplate) }, { auto: true }, ], }) export const volumeTest = base .extend({ volume: [ // eslint-disable-next-line no-empty-pattern async ({}, use) => { const volume = await Volume.create(`test-vol-${generateRandomString()}`) onTestFailed(() => { console.error(`\n[TEST FAILED] Volume ID: ${volume.volumeId}`) }) try { await use(volume) } finally { try { await Volume.destroy(volume.volumeId) } catch { // Ignore cleanup errors } } }, { auto: false }, ], }) .skipIf(process.env.ENABLE_VOLUME_TESTS === undefined) export const isDebug = process.env.E2B_DEBUG !== undefined export const isIntegrationTest = process.env.E2B_INTEGRATION_TEST !== undefined /** Placeholder API key with a valid format for tests that don't hit the API. */ export const TEST_API_KEY = `e2b_${'0'.repeat(40)}` function generateRandomString(length: number = 8): string { return Math.random() .toString(36) .substring(2, length + 2) } export async function wait(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)) } /** * Returns the API URL for the given path, using E2B_DOMAIN env var. * Supports msw path parameters like :templateID */ export function apiUrl(path: string): string { const domain = process.env.E2B_DOMAIN || 'e2b.app' return `https://api.${domain}${path}` } export { template }