Files
2026-07-13 12:47:58 +08:00

352 lines
10 KiB
TypeScript

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')
})
})
})