import { afterEach, beforeEach, describe, expect, test } from 'vitest' import * as fs from 'fs/promises' import * as path from 'path' import { execSync } from 'child_process' import { Language } from '../../../src/commands/template/generators' interface FileNames { template: string buildDev: string buildProd: string } function getFileNames(language: Language): FileNames { switch (language) { case Language.TypeScript: { return { template: 'template.ts', buildDev: 'build.dev.ts', buildProd: 'build.prod.ts', } } case Language.PythonSync: case Language.PythonAsync: { return { template: 'template.py', buildDev: 'build_dev.py', buildProd: 'build_prod.py', } } default: throw new Error(`Unsupported language: ${language}`) } } describe('Template Migration', () => { let testDir: string const cliPath = path.join(process.cwd(), 'dist', 'index.js') const fixturesDir = path.join(__dirname, 'fixtures') beforeEach(async () => { // Use Node.js built-in temp directory handling testDir = await fs.mkdtemp('e2b-migrate-test-') }) afterEach(async () => { // Clean up test directory if (testDir) { await fs.rm(testDir, { recursive: true, force: true }) } }) // Run tests for each test case describe('Migration Test Cases', () => { // Test case names correspond to fixture directory names const testCases = [ 'complex-python', 'copy-variations', 'custom-commands', 'minimal-dockerfile', 'multiple-env', 'start-cmd', 'multi-stage', ] testCases.forEach((testCaseName) => { describe(testCaseName.replace('-', ' '), () => { test('should migrate to TypeScript', async () => { await runMigrationTest(testCaseName, Language.TypeScript) }) test('should migrate to Python sync', async () => { await runMigrationTest(testCaseName, Language.PythonSync) }) test('should migrate to Python async', async () => { await runMigrationTest(testCaseName, Language.PythonAsync) }) }) }) async function runMigrationTest(testCaseName: string, language: Language) { const fixtureDir = path.join(fixturesDir, testCaseName) // Copy fixture files to test directory await copyFixtureFiles(fixtureDir, testDir) // Run migration execSync(`node ${cliPath} template migrate --language ${language}`, { cwd: testDir, }) // Determine file extensions and names based on language const fileNames = getFileNames(language) // Load expected outputs const expectedDir = path.join(fixtureDir, 'expected', language) const expectedTemplate = await fs.readFile( path.join(expectedDir, fileNames.template), 'utf-8' ) const expectedBuildDev = await fs.readFile( path.join(expectedDir, fileNames.buildDev), 'utf-8' ) const expectedBuildProd = await fs.readFile( path.join(expectedDir, fileNames.buildProd), 'utf-8' ) // Check generated files const templateFile = await fs.readFile( path.join(testDir, fileNames.template), 'utf-8' ) expect(templateFile.trim()).toEqual(expectedTemplate.trim()) const buildDevFile = await fs.readFile( path.join(testDir, fileNames.buildDev), 'utf-8' ) expect(buildDevFile.trim()).toEqual(expectedBuildDev.trim()) const buildProdFile = await fs.readFile( path.join(testDir, fileNames.buildProd), 'utf-8' ) expect(buildProdFile.trim()).toEqual(expectedBuildProd.trim()) if ( language === Language.PythonSync || language === Language.PythonAsync ) { for (const content of [buildDevFile, buildProdFile]) { expect(content).toContain('from template import template') expect(content).not.toContain('from .template import template') } } // Check that old files are renamed to .old extensions const oldDockerfilePath = path.join(testDir, 'e2b.Dockerfile.old') const oldConfigPath = path.join(testDir, 'e2b.toml.old') expect( await fs .access(oldDockerfilePath) .then(() => true) .catch(() => false) ).toBe(true) expect( await fs .access(oldConfigPath) .then(() => true) .catch(() => false) ).toBe(true) // Verify original files no longer exist const originalDockerfilePath = path.join(testDir, 'e2b.Dockerfile') const originalConfigPath = path.join(testDir, 'e2b.toml') expect( await fs .access(originalDockerfilePath) .then(() => true) .catch(() => false) ).toBe(false) expect( await fs .access(originalConfigPath) .then(() => true) .catch(() => false) ).toBe(false) } async function copyFixtureFiles(fixtureDir: string, targetDir: string) { // Copy Dockerfile and config await fs.copyFile( path.join(fixtureDir, 'e2b.Dockerfile'), path.join(targetDir, 'e2b.Dockerfile') ) await fs.copyFile( path.join(fixtureDir, 'e2b.toml'), path.join(targetDir, 'e2b.toml') ) } }) describe('Override Options', () => { test('should apply name, command and resource overrides', async () => { const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) const config = `template_id = "config-name" dockerfile = "e2b.Dockerfile" cpu_count = 2 memory_mb = 512` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) execSync( `node ${cliPath} template migrate --language typescript --name " Overridden-Name " --cmd "node server.js" --ready-cmd "curl localhost:3000" --cpu-count 4 --memory-mb 2048`, { cwd: testDir, } ) const templateFile = await fs.readFile( path.join(testDir, 'template.ts'), 'utf-8' ) expect(templateFile).toContain( ".setStartCmd('sudo node server.js', 'curl localhost:3000')" ) const buildProdFile = await fs.readFile( path.join(testDir, 'build.prod.ts'), 'utf-8' ) expect(buildProdFile).toContain("'overridden-name'") expect(buildProdFile).not.toContain("'config-name'") expect(buildProdFile).toContain('cpuCount: 4') expect(buildProdFile).toContain('memoryMB: 2048') }) test('should reject an invalid --name', async () => { const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) const config = `template_id = "config-name" dockerfile = "e2b.Dockerfile"` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) expect(() => { execSync( `node ${cliPath} template migrate --language typescript --name "Invalid Name"`, { cwd: testDir, } ) }).toThrow() }) test('should reject non-numeric resource overrides', async () => { const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) const config = `template_id = "config-name" dockerfile = "e2b.Dockerfile"` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) expect(() => { execSync( `node ${cliPath} template migrate --language typescript --cpu-count abc`, { cwd: testDir, } ) }).toThrow() expect(() => { execSync( `node ${cliPath} template migrate --language typescript --memory-mb abc`, { cwd: testDir, } ) }).toThrow() }) test('should reject an odd memory override', async () => { const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) const config = `template_id = "resources" dockerfile = "e2b.Dockerfile"` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) expect(() => { execSync( `node ${cliPath} template migrate --language typescript --memory-mb 1023`, { cwd: testDir, } ) }).toThrow() }) }) describe('Error Cases', () => { test('should succeed with warning when config file is missing', async () => { // Create only Dockerfile, no config const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) // Run migration and expect it to succeed with warning (capture stderr + stdout) const output = execSync( `node ${cliPath} template migrate --language typescript 2>&1`, { cwd: testDir, encoding: 'utf-8', } ) expect(output).toContain( 'Config file ./e2b.toml not found. Using defaults.' ) expect(output).toContain('Migration completed successfully') }) test('should fail gracefully when Dockerfile is missing', async () => { // Create only config, no Dockerfile const config = `template_id = "test-app" dockerfile = "e2b.Dockerfile"` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) // Run migration and expect it to fail expect(() => { execSync(`node ${cliPath} template migrate --language typescript`, { cwd: testDir, }) }).toThrow() }) }) describe('File Collision Handling', () => { test('should error out if files already exist', async () => { // Create test Dockerfile const dockerfile = 'FROM node:18' await fs.writeFile(path.join(testDir, 'e2b.Dockerfile'), dockerfile) // Create test config const config = `template_id = "test-app" dockerfile = "e2b.Dockerfile"` await fs.writeFile(path.join(testDir, 'e2b.toml'), config) // Create existing files await fs.writeFile(path.join(testDir, 'template.ts'), '// existing') await fs.writeFile(path.join(testDir, 'build.dev.ts'), '// existing') await fs.writeFile(path.join(testDir, 'build.prod.ts'), '// existing') // Run migration expect(() => { execSync(`node ${cliPath} template migrate --language typescript`, { cwd: testDir, }) }).toThrow() const files = await fs.readdir(testDir) expect(files).toContain('template.ts') expect(files).toContain('build.dev.ts') expect(files).toContain('build.prod.ts') }) }) })