diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index e184b19eda..3a6dab7184 100644 --- a/packages/js-sdk/tests/api/list.test.ts +++ b/packages/js-sdk/tests/api/list.test.ts @@ -64,7 +64,7 @@ sandboxTest.skipIf(isDebug)( async ({ sandboxTestId }) => { // Create and pause a sandbox const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - await extraSbx.betaPause() + await Sandbox.pause(extraSbx.sandboxId) try { const paginator = Sandbox.list({ @@ -298,221 +298,3 @@ sandboxTest.skipIf(isDebug)( assert.equal(unknownSandboxes.length, 0) } ) - -sandboxTest.skipIf(isDebug)( - 'list sandboxes', - async ({ sandbox, sandboxTestId }) => { - const paginator = Sandbox.list({ - query: { metadata: { sandboxTestId } }, - }) - const sandboxes = await paginator.nextItems() - - assert.isAtLeast(sandboxes.length, 1) - - const found = sandboxes.some((s) => s.sandboxId === sandbox.sandboxId) - assert.isTrue(found) - } -) - -sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => { - const uniqueId = crypto.randomUUID() - const extraSbx = await Sandbox.create({ metadata: { uniqueId } }) - - try { - const paginator = Sandbox.list({ - query: { metadata: { uniqueId } }, - }) - const sandboxes = await paginator.nextItems() - - assert.equal(sandboxes.length, 1) - assert.equal(sandboxes[0].sandboxId, extraSbx.sandboxId) - } finally { - await extraSbx.kill() - } -}) - -sandboxTest.skipIf(isDebug)( - 'list running sandboxes', - async ({ sandboxTestId }) => { - const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - - try { - const paginator = Sandbox.list({ - query: { metadata: { sandboxTestId }, state: ['running'] }, - }) - const sandboxes = await paginator.nextItems() - - assert.isAtLeast(sandboxes.length, 1) - - // Verify our running sandbox is in the list - const found = sandboxes.some( - (s) => s.sandboxId === extraSbx.sandboxId && s.state === 'running' - ) - assert.isTrue(found) - } finally { - await extraSbx.kill() - } - } -) - -sandboxTest.skipIf(isDebug)( - 'list paused sandboxes', - async ({ sandboxTestId }) => { - // Create and pause a sandbox - const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - await Sandbox.betaPause(extraSbx.sandboxId) - - try { - const paginator = Sandbox.list({ - query: { metadata: { sandboxTestId }, state: ['paused'] }, - }) - const sandboxes = await paginator.nextItems() - - assert.isAtLeast(sandboxes.length, 1) - - // Verify our paused sandbox is in the list - const found = sandboxes.some( - (s) => s.sandboxId === extraSbx.sandboxId && s.state === 'paused' - ) - assert.isTrue(found) - } finally { - await extraSbx.kill() - } - } -) - -sandboxTest.skipIf(isDebug)( - 'paginate running sandboxes', - async ({ sandbox, sandboxTestId }) => { - // Create extra sandboxes - const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - - try { - // Test pagination with limit - const paginator = Sandbox.list({ - limit: 1, - query: { metadata: { sandboxTestId }, state: ['running'] }, - }) - const sandboxes = await paginator.nextItems() - - // Check first page - assert.equal(sandboxes.length, 1) - assert.equal(sandboxes[0].state, 'running') - assert.isTrue(paginator.hasNext) - assert.notEqual(paginator.nextToken, undefined) - assert.equal(sandboxes[0].sandboxId, extraSbx.sandboxId) - - // Get second page - const sandboxes2 = await paginator.nextItems() - - // Check second page - assert.equal(sandboxes2.length, 1) - assert.equal(sandboxes2[0].state, 'running') - assert.isFalse(paginator.hasNext) - assert.equal(paginator.nextToken, undefined) - assert.equal(sandboxes2[0].sandboxId, sandbox.sandboxId) - } finally { - await extraSbx.kill() - } - } -) - -sandboxTest.skipIf(isDebug)( - 'paginate paused sandboxes', - async ({ sandbox, sandboxTestId }) => { - await Sandbox.betaPause(sandbox.sandboxId) - - // Create extra paused sandbox - const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - await Sandbox.betaPause(extraSbx.sandboxId) - - try { - // Test pagination with limit - const paginator = Sandbox.list({ - limit: 1, - query: { metadata: { sandboxTestId }, state: ['paused'] }, - }) - const sandboxes = await paginator.nextItems() - - // Check first page - assert.equal(sandboxes.length, 1) - assert.equal(sandboxes[0].state, 'paused') - assert.isTrue(paginator.hasNext) - assert.notEqual(paginator.nextToken, undefined) - assert.equal(sandboxes[0].sandboxId, extraSbx.sandboxId) - - // Get second page - const sandboxes2 = await paginator.nextItems() - - // Check second page - assert.equal(sandboxes2.length, 1) - assert.equal(sandboxes2[0].state, 'paused') - assert.isFalse(paginator.hasNext) - assert.equal(paginator.nextToken, undefined) - assert.equal(sandboxes2[0].sandboxId, sandbox.sandboxId) - } finally { - await extraSbx.kill() - } - } -) - -sandboxTest.skipIf(isDebug)( - 'paginate running and paused sandboxes', - async ({ sandbox, sandboxTestId }) => { - // Create extra sandbox - const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } }) - - // Pause the extra sandbox - await Sandbox.betaPause(sandbox.sandboxId) - - try { - // Test pagination with limit - const paginator = Sandbox.list({ - limit: 1, - query: { - metadata: { sandboxTestId }, - state: ['running', 'paused'], - }, - }) - const sandboxes = await paginator.nextItems() - - // Check first page - assert.equal(sandboxes.length, 1) - assert.equal(sandboxes[0].state, 'running') - - assert.isTrue(paginator.hasNext) - assert.notEqual(paginator.nextToken, undefined) - assert.equal(sandboxes[0].sandboxId, extraSbx.sandboxId) - - // Get second page - const sandboxes2 = await paginator.nextItems() - - // Check second page - assert.equal(sandboxes2.length, 1) - assert.equal(sandboxes2[0].state, 'paused') - assert.isFalse(paginator.hasNext) - assert.equal(paginator.nextToken, undefined) - assert.equal(sandboxes2[0].sandboxId, sandbox.sandboxId) - } finally { - await extraSbx.kill() - } - } -) - -sandboxTest.skipIf(isDebug)( - 'paginate iterator', - async ({ sandbox, sandboxTestId }) => { - const paginator = Sandbox.list({ - query: { metadata: { sandboxTestId } }, - }) - const sandboxes: SandboxInfo[] = [] - - while (paginator.hasNext) { - const sbxs = await paginator.nextItems() - sandboxes.push(...sbxs) - } - - assert.isAtLeast(sandboxes.length, 1) - assert.isTrue(sandboxes.some((s) => s.sandboxId === sandbox.sandboxId)) - } -) diff --git a/packages/js-sdk/tests/connectionConfig.test.ts b/packages/js-sdk/tests/connectionConfig.test.ts index 08bfbd128c..65eb4d7a5a 100644 --- a/packages/js-sdk/tests/connectionConfig.test.ts +++ b/packages/js-sdk/tests/connectionConfig.test.ts @@ -430,12 +430,6 @@ test('setupRequestController clearStartTimeout stops the handshake timer', async assert.equal(controller.signal.aborted, false) }) -test('setupRequestController handshake timer aborts when not cleared', async () => { - const { controller } = setupRequestController(20, undefined) - await new Promise((resolve) => setTimeout(resolve, 40)) - assert.equal(controller.signal.aborted, true) -}) - test('setupRequestController handshake timeout aborts with TimeoutError reason', async () => { const { controller } = setupRequestController(20, undefined) await new Promise((resolve) => setTimeout(resolve, 40)) diff --git a/packages/js-sdk/tests/envd/handleRpcError.test.ts b/packages/js-sdk/tests/envd/handleRpcError.test.ts index a3ffcc8bf3..9839143a49 100644 --- a/packages/js-sdk/tests/envd/handleRpcError.test.ts +++ b/packages/js-sdk/tests/envd/handleRpcError.test.ts @@ -49,16 +49,6 @@ function neverProbe(): Promise { throw new Error('health check should not run') } -describe('Unavailable with the sandbox not found', () => { - test('returns SandboxNotRunningError, not SandboxNotFoundError, from every call', () => { - const err = handleRpcError( - new ConnectError('The sandbox was not found', Code.Unavailable) - ) - assert.instanceOf(err, SandboxNotRunningError) - assert.notInstanceOf(err, SandboxNotFoundError) - }) -}) - describe('ambiguous Unavailable', () => { test('is an Unavailable that says neither not found nor port not open', () => { assert.isTrue( @@ -148,6 +138,7 @@ describe('handleRpcError', () => { test('returns SandboxUnreachableError for Unavailable with the sandbox running but its port closed', () => { const err = handleRpcError(new ConnectError(PORT_CLOSED, Code.Unavailable)) assert.instanceOf(err, SandboxUnreachableError) + assert.notInstanceOf(err, SandboxNotRunningError) assert.include(err.message, 'could not be reached') }) @@ -157,16 +148,11 @@ describe('handleRpcError', () => { ) assert.instanceOf(err, SandboxNotRunningError) assert.instanceOf(err, TimeoutError) + assert.notInstanceOf(err, SandboxNotFoundError) assert.notInstanceOf(err, SandboxUnreachableError) assert.include(err.message, 'The sandbox was not found') }) - test('returns SandboxUnreachableError, not SandboxNotRunningError, for Unavailable with the port closed', () => { - const err = handleRpcError(new ConnectError(PORT_CLOSED, Code.Unavailable)) - assert.instanceOf(err, SandboxUnreachableError) - assert.notInstanceOf(err, SandboxNotRunningError) - }) - test('returns TimeoutError for Unavailable', () => { const err = handleRpcError(new ConnectError('gone', Code.Unavailable)) assert.instanceOf(err, TimeoutError) @@ -203,19 +189,6 @@ describe('handleRpcErrorWithHealthCheck', () => { Browser: 'network error', } - test('does not probe health on an Unavailable for the sandbox not found', async () => { - let probed = false - const err = await handleRpcErrorWithHealthCheck( - new ConnectError('The sandbox was not found', Code.Unavailable), - async () => { - probed = true - return true - } - ) - assert.isFalse(probed) - assert.instanceOf(err, SandboxNotRunningError) - }) - test('returns a SandboxNotRunningError when the health check says the sandbox is not running', async () => { const original = terminated() const err = await handleRpcErrorWithHealthCheck(original, async () => false) diff --git a/packages/js-sdk/tests/envd/http2.test.ts b/packages/js-sdk/tests/envd/http2.test.ts index 841f8d3d28..24841cb9bb 100644 --- a/packages/js-sdk/tests/envd/http2.test.ts +++ b/packages/js-sdk/tests/envd/http2.test.ts @@ -193,28 +193,6 @@ test('passes Request objects to undici as URL plus init', async () => { expect(requests[0].init?.body).toBeInstanceOf(ReadableStream) }) -test('can create a bounded dispatcher for RPC streams', async () => { - const agents: Array<{ allowH2?: boolean; connections?: number }> = [] - - class Agent { - constructor(options: { allowH2?: boolean; connections?: number }) { - agents.push(options) - } - } - - const undiciFetch = vi.fn(() => Promise.resolve(new Response('ok'))) - - const { createEnvdFetchForRuntime } = await import('../../src/envd/http2') - - const fetcher = createEnvdFetchForRuntime('node', { - connectionLimit: 100, - loadUndici: () => Promise.resolve({ Agent, fetch: undiciFetch }), - }) - await fetcher('https://example.com/rpc') - - expect(agents).toEqual([{ allowH2: true, connections: 100 }]) -}) - test('reads RPC stream dispatcher connection limit from env', async () => { process.env.E2B_ENVD_RPC_CONNECTIONS = '200' diff --git a/packages/js-sdk/tests/retry.test.ts b/packages/js-sdk/tests/retry.test.ts index 88aa092351..7cf921eda7 100644 --- a/packages/js-sdk/tests/retry.test.ts +++ b/packages/js-sdk/tests/retry.test.ts @@ -339,47 +339,6 @@ test.each([ } ) -test('retries a POST when the server marks a 503', async () => { - const statuses = [503, 200] - const fetchImpl = vi.fn(async () => { - const responseStatus = statuses.shift()! - return new Response(null, { - status: responseStatus, - headers: responseStatus === 200 ? {} : { 'Should-Retry': 'true' }, - }) - }) as typeof fetch - const fetchWithRetry = withRetry(fetchImpl, 3, 60_000, { - monotonic: () => 0, - sleep: async () => {}, - }) - - const response = await fetchWithRetry('https://api.e2b.test/sandboxes', { - method: 'POST', - }) - - expect(response.status).toBe(200) - expect(fetchImpl).toHaveBeenCalledTimes(2) -}) - -test('retries a POST 502 without Should-Retry', async () => { - const responses = [ - new Response(null, { status: 502, headers: { 'Retry-After': '0' } }), - new Response('ok'), - ] - const fetchImpl = vi.fn(async () => responses.shift()!) as typeof fetch - const fetchWithRetry = withRetry(fetchImpl, 1, 10_000, { - monotonic: () => 0, - sleep: async () => {}, - }) - - const response = await fetchWithRetry('https://api.e2b.test/v2/sandboxes', { - method: 'POST', - }) - - expect(response.status).toBe(200) - expect(fetchImpl).toHaveBeenCalledTimes(2) -}) - test('caps the backoff for long retry sequences', async () => { const fetchImpl = vi.fn( async () => diff --git a/packages/js-sdk/tests/sandbox/configPropagation.test.ts b/packages/js-sdk/tests/sandbox/configPropagation.test.ts index 98ec5017f0..0432bb014b 100644 --- a/packages/js-sdk/tests/sandbox/configPropagation.test.ts +++ b/packages/js-sdk/tests/sandbox/configPropagation.test.ts @@ -55,14 +55,6 @@ describe('Sandbox API config propagation', () => { assert.equal(opts?.headers?.['X-Test'], baseConfig.apiHeaders['X-Test']) }) - test('accepts apiHeaders in static Sandbox API options', () => { - const opts = { apiHeaders: baseConfig.apiHeaders } satisfies Parameters< - typeof Sandbox.kill - >[1] - - assert.equal(opts.apiHeaders['X-Test'], baseConfig.apiHeaders['X-Test']) - }) - test('lets public method call overrides win over connectionConfig', async () => { const pauseSpy = vi.spyOn(SandboxApi, 'pause').mockResolvedValue(true) const sandbox = createSandbox() diff --git a/packages/js-sdk/tests/sandbox/files/info.test.ts b/packages/js-sdk/tests/sandbox/files/info.test.ts index 7e5bd46699..12c8d982f9 100644 --- a/packages/js-sdk/tests/sandbox/files/info.test.ts +++ b/packages/js-sdk/tests/sandbox/files/info.test.ts @@ -47,17 +47,6 @@ sandboxTest('get info of a directory', async ({ sandbox }) => { assert.property(info, 'modifiedTime') }) -sandboxTest( - 'get info of a directory that does not exist', - async ({ sandbox }) => { - const dirname = 'test_does_not_exist_dir' - - await expect(sandbox.files.getInfo(dirname)).rejects.toThrow( - FileNotFoundError - ) - } -) - sandboxTest('get info of a symlink', async ({ sandbox }) => { const filename = 'test_file.txt' diff --git a/packages/js-sdk/tests/sandbox/files/list.test.ts b/packages/js-sdk/tests/sandbox/files/list.test.ts index fbadab62a6..169ef2b8dc 100644 --- a/packages/js-sdk/tests/sandbox/files/list.test.ts +++ b/packages/js-sdk/tests/sandbox/files/list.test.ts @@ -120,51 +120,6 @@ sandboxTest('list directory with invalid depth', async ({ sandbox }) => { } }) -sandboxTest('file entry details', async ({ sandbox }) => { - const testDir = 'test-file-entry' - const filePath = `${testDir}/test.txt` - const content = 'Hello, World!' - - await sandbox.files.makeDir(testDir) - await sandbox.files.write(filePath, content) - - const files = await sandbox.files.list(testDir, { depth: 1 }) - assert.equal(files.length, 1) - - const fileEntry = files[0] - assert.equal(fileEntry.name, 'test.txt') - assert.equal(fileEntry.path, `/home/user/${filePath}`) - assert.equal(fileEntry.type, 'file') - assert.equal(fileEntry.mode, 0o644) - assert.equal(fileEntry.permissions, '-rw-r--r--') - assert.equal(fileEntry.owner, 'user') - assert.equal(fileEntry.group, 'user') - assert.equal(fileEntry.size, content.length) - assert.ok(fileEntry.modifiedTime) - assert.isUndefined(fileEntry.symlinkTarget) -}) - -sandboxTest('directory entry details', async ({ sandbox }) => { - const testDir = 'test-entry-info' - const subDir = `${testDir}/subdir` - - await sandbox.files.makeDir(testDir) - await sandbox.files.makeDir(subDir) - - const files = await sandbox.files.list(testDir, { depth: 1 }) - assert.equal(files.length, 1) - - const dirEntry = files[0] - assert.equal(dirEntry.name, 'subdir') - assert.equal(dirEntry.path, `/home/user/${subDir}`) - assert.equal(dirEntry.type, 'dir') - assert.equal(dirEntry.mode, 0o755) - assert.equal(dirEntry.permissions, 'drwxr-xr-x') - assert.equal(dirEntry.owner, 'user') - assert.equal(dirEntry.group, 'user') - assert.ok(dirEntry.modifiedTime) -}) - sandboxTest('mixed entries (files and directories)', async ({ sandbox }) => { const testDir = 'test-mixed-entries' const subDir = `${testDir}/subdir` @@ -203,4 +158,5 @@ sandboxTest('mixed entries (files and directories)', async ({ sandbox }) => { assert.equal(fileEntry!.group, 'user') assert.equal(fileEntry!.size, content.length) assert.ok(fileEntry!.modifiedTime) + assert.isUndefined(fileEntry!.symlinkTarget) }) diff --git a/packages/js-sdk/tests/sandbox/files/signing.test.ts b/packages/js-sdk/tests/sandbox/files/signing.test.ts index 8d319c02c1..fabf3ad734 100644 --- a/packages/js-sdk/tests/sandbox/files/signing.test.ts +++ b/packages/js-sdk/tests/sandbox/files/signing.test.ts @@ -134,13 +134,4 @@ describe('file signing', () => { }) } ) - - sandboxTest.skipIf(isDebug)( - 'test command run with secured sbx', - async ({ sandbox }) => { - const response = await sandbox.commands.run('echo Hello World!') - - assert.equal(response.stdout, 'Hello World!\n') - } - ) }) diff --git a/packages/js-sdk/tests/sandbox/files/write.test.ts b/packages/js-sdk/tests/sandbox/files/write.test.ts index 0abc927b36..5654fa6f37 100644 --- a/packages/js-sdk/tests/sandbox/files/write.test.ts +++ b/packages/js-sdk/tests/sandbox/files/write.test.ts @@ -128,26 +128,6 @@ sandboxTest('write multiple files', async ({ sandbox }) => { } }) -sandboxTest('write file', async ({ sandbox }) => { - const filename = 'test_write.txt' - const content = 'This is a test file.' - - const info = await sandbox.files.write(filename, content) - assert.isFalse(Array.isArray(info)) - assert.equal(info.name, filename) - assert.equal(info.type, 'file') - assert.equal(info.path, `/home/user/${filename}`) - - const exists = await sandbox.files.exists(filename) - assert.isTrue(exists) - const readContent = await sandbox.files.read(filename) - assert.equal(readContent, content) - - if (isDebug) { - await sandbox.files.remove(filename) - } -}) - sandboxTest('overwrite file', async ({ sandbox }) => { const filename = 'test_overwrite.txt' const initialContent = 'Initial content.' diff --git a/packages/js-sdk/tests/sandbox/iam.test.ts b/packages/js-sdk/tests/sandbox/iam.test.ts index 662a78a465..06d4f481ed 100644 --- a/packages/js-sdk/tests/sandbox/iam.test.ts +++ b/packages/js-sdk/tests/sandbox/iam.test.ts @@ -143,6 +143,19 @@ test('the token map serializes, awaits and coerces like a plain object', async ( expect(await tokens).toBe(tokens) }) +test.for(RUNTIME_PROBED_PROPS)( + 'the unchecked token map resolves %s to its placeholder', + (prop: string) => { + // The update-network payload carries no iam config, so no name can be + // reported as unregistered. + const tokens = iamTokenPlaceholders([], { validate: false }) + + expect(`${tokens[prop]}`).toBe(`\${e2b.identity.tokens.${prop}}`) + expect(JSON.stringify(tokens)).toBe('{}') + expect(String(tokens)).toBe('[object Object]') + } +) + test.for(RUNTIME_PROBED_PROPS)( 'reading the unregistered token %s throws instead of resolving', (prop: string) => { @@ -169,16 +182,3 @@ test('the runtime-probed valueOf answers with the guarded map', () => { /iam token 'gcp'.*Registered tokens: 'aws'/s ) }) - -test.for(RUNTIME_PROBED_PROPS)( - 'the unchecked token map resolves %s to its placeholder', - (prop: string) => { - // The update-network payload carries no iam config, so no name can be - // reported as unregistered. - const tokens = iamTokenPlaceholders([], { validate: false }) - - expect(`${tokens[prop]}`).toBe(`\${e2b.identity.tokens.${prop}}`) - expect(JSON.stringify(tokens)).toBe('{}') - expect(String(tokens)).toBe('[object Object]') - } -) diff --git a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts b/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts deleted file mode 100644 index c64c2fbee5..0000000000 --- a/packages/js-sdk/tests/sandbox/pty/sendInput.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from 'vitest' -import { sandboxTest } from '../../setup' - -sandboxTest('send input', async ({ sandbox }) => { - const terminal = await sandbox.pty.create({ - cols: 80, - rows: 24, - onData: () => null, - }) - - await sandbox.pty.sendInput(terminal.pid, new TextEncoder().encode('exit\n')) - - await terminal.wait() - expect(terminal.exitCode).toBe(0) -}) diff --git a/packages/js-sdk/tests/sandbox/secure.test.ts b/packages/js-sdk/tests/sandbox/secure.test.ts index 3c71c8eb7f..25b14b53cf 100644 --- a/packages/js-sdk/tests/sandbox/secure.test.ts +++ b/packages/js-sdk/tests/sandbox/secure.test.ts @@ -43,29 +43,6 @@ describe('secure sandbox', () => { ) }) -test.skipIf(isDebug)('signing generation', async () => { - const operation = 'read' - const path = '/home/user/hello.txt' - const user = 'root' - const envdAccessToken = crypto.randomUUID() - - const signatureRaw = `${path}:${operation}:${user}:${envdAccessToken}` - - const readSignatureExpected = { - signature: await expectedSignature(signatureRaw), - expiration: null, - } - - const readSignatureReceived = await getSignature({ - path, - operation, - user, - envdAccessToken, - }) - - assert.deepEqual(readSignatureExpected, readSignatureReceived) -}) - test.skipIf(isDebug)('signing generation with expiration', async () => { const operation = 'read' const path = '/home/user/hello.txt' diff --git a/packages/js-sdk/tests/template/utils/getCallerDirectory.test.ts b/packages/js-sdk/tests/template/utils/getCallerDirectory.test.ts index e9de0910de..967323b681 100644 --- a/packages/js-sdk/tests/template/utils/getCallerDirectory.test.ts +++ b/packages/js-sdk/tests/template/utils/getCallerDirectory.test.ts @@ -1,7 +1,5 @@ import { expect, test } from 'vitest' -import os from 'node:os' import path from 'node:path' -import { fileURLToPath, pathToFileURL } from 'node:url' import { getCallerDirectory, getCallerFrame } from '../../../src/template/utils' test('getCallerDirectory', () => { @@ -22,16 +20,3 @@ test('getCallerFrame starts at the first frame outside the SDK', () => { const firstFrame = stackTrace!.split('\n')[0] expect(firstFrame).toContain(path.basename(__filename)) }) - -test('getCallerDirectory handles file:// URLs from ESM modules', () => { - // In ESM modules, CallSite.getFileName() returns file:// URLs - // This test verifies that the conversion works correctly - const testDirectoryPath = path.join(os.tmpdir(), 'test', 'project', 'src') - const testFilePath = path.join(testDirectoryPath, 'template.ts') - const fileUrl = pathToFileURL(testFilePath) - - // Verify that fileURLToPath correctly converts the URL - // This is the same conversion used in getCallerDirectory - expect(fileURLToPath(fileUrl)).toBe(testFilePath) - expect(path.dirname(fileURLToPath(fileUrl))).toBe(testDirectoryPath) -}) diff --git a/packages/js-sdk/tests/template/utils/validateRelativePath.test.ts b/packages/js-sdk/tests/template/utils/validateRelativePath.test.ts index 7c6e22e774..16ee778a07 100644 --- a/packages/js-sdk/tests/template/utils/validateRelativePath.test.ts +++ b/packages/js-sdk/tests/template/utils/validateRelativePath.test.ts @@ -96,12 +96,6 @@ describe('validateRelativePath', () => { ) }) - test('rejects parent directory escape with forward slash', () => { - expect(() => validateRelativePath('../file.txt', undefined)).toThrow( - TemplateError - ) - }) - test.skipIf(!isWindows)( 'rejects parent directory escape with backslash', () => { diff --git a/packages/js-sdk/tests/undici.test.ts b/packages/js-sdk/tests/undici.test.ts index 9264b9dc2d..26dd889f20 100644 --- a/packages/js-sdk/tests/undici.test.ts +++ b/packages/js-sdk/tests/undici.test.ts @@ -134,19 +134,6 @@ test('adopts the body of a Request from another fetch implementation', async () expect(await new Response(seen[0].init?.body).text()).toBe('hello') }) -// loadUndici is only reached in production when the runtime is Node; other -// runtimes (Bun, Deno) use their global fetch instead. -test.skipIf(runtime !== 'node')( - 'loads a real undici module at runtime on Node', - async () => { - const undici = await loadUndici() - - expect(undici).toBeDefined() - expect(typeof undici?.fetch).toBe('function') - expect(typeof undici?.Agent).toBe('function') - } -) - // The end of the story the fake undici above can only imply: real undici, // asked to send a Request it did not mint, throws before touching the network. test.skipIf(runtime !== 'node')( diff --git a/packages/js-sdk/tests/volume/file.test.ts b/packages/js-sdk/tests/volume/file.test.ts index 2589ddd774..e1f40ec54c 100644 --- a/packages/js-sdk/tests/volume/file.test.ts +++ b/packages/js-sdk/tests/volume/file.test.ts @@ -64,9 +64,8 @@ describe('Volume File Operations', () => { volumeTest( 'should write and read a file with bytes format', async ({ volume }) => { - const path = '/test-bytes.txt' - const content = 'Test bytes content' - const contentBytes = new TextEncoder().encode(content) + const path = '/test-bytes.bin' + const contentBytes = new Uint8Array([0, 1, 2, 3, 4]) await volume.writeFile(path, contentBytes.buffer) const readBytes = await volume.readFile(path, { format: 'bytes' }) @@ -348,15 +347,6 @@ describe('Volume File Operations', () => { expect(entries.some((e) => e.name === 'file.txt')).toBe(true) }) - volumeTest.skip('should list with depth option', async ({ volume }) => { - await volume.makeDir('/deep/nested/structure', { force: true }) - await volume.writeFile('/deep/nested/structure/file.txt', 'Content') - - const entries = await volume.list('/deep', { depth: 2 }) - - expect(entries.length).toBeGreaterThan(0) - }) - volumeTest( 'should throw VolumePathNotFoundError for non-existent directory', async ({ volume }) => { @@ -386,16 +376,6 @@ describe('Volume File Operations', () => { expect(await volume.exists(path)).toBe(false) }) - volumeTest('should remove a directory recursively', async ({ volume }) => { - const dirPath = '/recursive-dir' - await volume.makeDir(`${dirPath}/nested`, { force: true }) - await volume.writeFile(`${dirPath}/nested/file.txt`, 'Content') - - await volume.remove(dirPath) - - expect(await volume.exists(dirPath)).toBe(false) - }) - volumeTest( 'should throw VolumePathNotFoundError when removing non-existent file', async ({ volume }) => { diff --git a/packages/js-sdk/tests/volume/volume.test.ts b/packages/js-sdk/tests/volume/volume.test.ts index a308d0d6f5..19ea0e19eb 100644 --- a/packages/js-sdk/tests/volume/volume.test.ts +++ b/packages/js-sdk/tests/volume/volume.test.ts @@ -200,29 +200,6 @@ describe('Volume CRUD', () => { const connected = await Volume.connect(vol.volumeId, { httpVersion: '1.1' }) expect(connected.httpVersion).toBe('1.1') }) - - it('should handle full lifecycle: create, get, list, destroy', async () => { - // Create - const vol = await Volume.create('lifecycle-vol') - expect(vol.name).toBe('lifecycle-vol') - - // Get info - const info = await Volume.getInfo(vol.volumeId) - expect(info.name).toBe('lifecycle-vol') - - // List - should contain the volume - const list = await Volume.list() - expect(list).toHaveLength(1) - expect(list[0].volumeId).toBe(vol.volumeId) - - // Destroy - const destroyed = await Volume.destroy(vol.volumeId) - expect(destroyed).toBe(true) - - // List again - should be empty - const listAfter = await Volume.list() - expect(listAfter).toHaveLength(0) - }) }) describe('Volume BYOC domain', () => { @@ -320,26 +297,6 @@ describe('Volume content readFile', () => { } ) - it('should return empty values for an empty file in every format', async () => { - volumeFiles.set('empty.txt', '') - const vol = await Volume.create('content-volume') - - const text = await vol.readFile('empty.txt') - expect(text).toBe('') - - const bytes = await vol.readFile('empty.txt', { format: 'bytes' }) - expect(bytes).toBeInstanceOf(Uint8Array) - expect(bytes.length).toBe(0) - - const blob = await vol.readFile('empty.txt', { format: 'blob' }) - expect(blob).toBeInstanceOf(Blob) - expect(blob.size).toBe(0) - - const stream = await vol.readFile('empty.txt', { format: 'stream' }) - expect(stream).toBeInstanceOf(ReadableStream) - expect(await new Response(stream).text()).toBe('') - }) - it('should throw VolumePathNotFoundError for a missing file', async () => { const vol = await Volume.create('content-volume') diff --git a/packages/python-sdk/tests/async/sandbox_async/commands/test_env_vars.py b/packages/python-sdk/tests/async/sandbox_async/commands/test_env_vars.py index 9563cac71c..aa5a27f818 100644 --- a/packages/python-sdk/tests/async/sandbox_async/commands/test_env_vars.py +++ b/packages/python-sdk/tests/async/sandbox_async/commands/test_env_vars.py @@ -3,11 +3,6 @@ from e2b import AsyncSandbox -async def test_command_envs(async_sandbox: AsyncSandbox): - cmd = await async_sandbox.commands.run("echo $FOO", envs={"FOO": "bar"}) - assert cmd.stdout.strip() == "bar" - - @pytest.mark.skip_debug() async def test_sandbox_envs(async_sandbox_factory): sbx = await async_sandbox_factory(envs={"FOO": "bar"}) diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py index a55a9f462c..779c4794e1 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_watch.py @@ -178,8 +178,3 @@ async def test_watch_file(async_sandbox: AsyncSandbox): with pytest.raises(SandboxException): await async_sandbox.files.watch_dir(filename, on_event=lambda e: None) - - -async def test_watch_file_with_secured_envd(async_sandbox): - await async_sandbox.files.watch_dir("/home/user/", on_event=lambda e: None) - await async_sandbox.files.write("test_watch.txt", "This file will be watched.") diff --git a/packages/python-sdk/tests/async/sandbox_async/files/test_write.py b/packages/python-sdk/tests/async/sandbox_async/files/test_write.py index be2dbd99cd..78eec0ccbc 100644 --- a/packages/python-sdk/tests/async/sandbox_async/files/test_write.py +++ b/packages/python-sdk/tests/async/sandbox_async/files/test_write.py @@ -127,25 +127,6 @@ async def test_write_to_non_existing_directory(async_sandbox: AsyncSandbox, debu await async_sandbox.files.remove(filename) -async def test_write_with_secured_envd(async_sandbox_factory): - filename = f"non_existing_dir_{uuid.uuid4()}/test_write.txt" - content = "This should succeed too." - - sbx = await async_sandbox_factory(timeout=30) - - assert await sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - await sbx.files.write(filename, content) - - exists = await sbx.files.exists(filename) - assert exists - - read_content = await sbx.files.read(filename) - assert read_content == content - - async def test_write_files_with_different_data_types( async_sandbox: AsyncSandbox, debug ): diff --git a/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py b/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py deleted file mode 100644 index d54eeaa09f..0000000000 --- a/packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py +++ /dev/null @@ -1,11 +0,0 @@ -from e2b import AsyncSandbox -from e2b.sandbox.commands.command_handle import PtySize - - -async def test_send_input(async_sandbox: AsyncSandbox): - terminal = await async_sandbox.pty.create( - PtySize(cols=80, rows=24), on_data=lambda x: print(x) - ) - await async_sandbox.pty.send_stdin(terminal.pid, b"exit\n") - await terminal.wait() - assert terminal.exit_code == 0 diff --git a/packages/python-sdk/tests/async/sandbox_async/test_network.py b/packages/python-sdk/tests/async/sandbox_async/test_network.py index 953891c563..0a4cfdf3e6 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_network.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_network.py @@ -1,6 +1,5 @@ import asyncio import json -from unittest.mock import AsyncMock import httpx import pytest @@ -36,15 +35,6 @@ async def wait_for_status( raise last_transport_error -async def test_wait_for_status_retries_transport_errors(): - response = httpx.Response(200) - client = AsyncMock() - client.get.side_effect = [httpx.ConnectError("not ready"), response] - - assert await wait_for_status(client, "https://sandbox.test", 200) is response - assert client.get.await_count == 2 - - @pytest.mark.skip_debug() async def test_allow_specific_ip_with_deny_all(async_sandbox_factory): """Test that sandbox with denyOut all and allowOut creates a whitelist.""" diff --git a/packages/python-sdk/tests/async/sandbox_async/test_secure.py b/packages/python-sdk/tests/async/sandbox_async/test_secure.py deleted file mode 100644 index 64261cca56..0000000000 --- a/packages/python-sdk/tests/async/sandbox_async/test_secure.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from e2b import AsyncSandbox - - -@pytest.mark.skip_debug() -async def test_start_secured(async_sandbox_factory): - sbx = await async_sandbox_factory(timeout=5) - - assert await sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - -@pytest.mark.skip_debug() -async def test_connect_to_secured(async_sandbox_factory): - sbx = await async_sandbox_factory(timeout=100) - - assert await sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - sbx_connection = await AsyncSandbox.connect(sbx.sandbox_id) - assert await sbx_connection.is_running() - assert sbx_connection._envd_version is not None - assert sbx_connection._envd_access_token is not None diff --git a/packages/python-sdk/tests/async/secret_async/test_secret.py b/packages/python-sdk/tests/async/secret_async/test_secret.py index 90d715a1c2..8e6995a639 100644 --- a/packages/python-sdk/tests/async/secret_async/test_secret.py +++ b/packages/python-sdk/tests/async/secret_async/test_secret.py @@ -8,7 +8,7 @@ from e2b.api.client.models.secret import Secret as SecretModel from e2b.api.client.models.secret_metadata import SecretMetadata from e2b.api.client.types import UNSET, Response -from e2b.exceptions import InvalidArgumentException, SecretNotFoundException +from e2b.exceptions import SecretNotFoundException import e2b.api.client.api.secrets.delete_secrets_secret_id as delete_secret_mod import e2b.api.client.api.secrets.get_secrets as get_secrets_mod import e2b.api.client.api.secrets.get_secrets_secret_id as get_secret_mod @@ -194,13 +194,3 @@ async def test_destroy_secret(): async def test_destroy_nonexistent_secret(): assert await AsyncSecret.destroy("missing") is False - - -def test_fill(): - assert AsyncSecret.fill("openai-api-key") == "${e2b.secrets.openai-api-key}" - - -@pytest.mark.parametrize("name", ["", "{name", "name}", "name\n", "name\x00"]) -def test_fill_invalid_name_raises(name): - with pytest.raises(InvalidArgumentException): - AsyncSecret.fill(name) diff --git a/packages/python-sdk/tests/async/template_async/methods/test_from_dockerfile.py b/packages/python-sdk/tests/async/template_async/methods/test_from_dockerfile.py deleted file mode 100644 index f164b1b179..0000000000 --- a/packages/python-sdk/tests/async/template_async/methods/test_from_dockerfile.py +++ /dev/null @@ -1,485 +0,0 @@ -import pytest - -from e2b import DockerfileSyntaxError, AsyncTemplate -from e2b.template.types import InstructionType - - -@pytest.mark.skip_debug() -async def test_from_dockerfile(): - dockerfile = """FROM node:24 -WORKDIR /app -COPY package.json . -RUN npm install -ENTRYPOINT ["sleep", "20"]""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - # base image - assert template._template._base_image == "node:24" - - instructions = template._template._instructions - - # Docker defaults - assert instructions[1]["type"] == InstructionType.WORKDIR - assert instructions[1]["args"][0] == "/" - - # Instructions from Dockerfile - assert instructions[2]["type"] == InstructionType.WORKDIR - assert instructions[2]["args"][0] == "/app" - - assert instructions[3]["type"] == InstructionType.COPY - assert instructions[3]["args"][0] == "package.json" - assert instructions[3]["args"][1] == "." - - assert instructions[4]["type"] == InstructionType.RUN - assert instructions[4]["args"][0] == "npm install" - - # E2B defaults appended - assert instructions[5]["type"] == InstructionType.USER - assert instructions[5]["args"][0] == "user" - - # Start command - assert template._template._start_cmd == "sleep 20" - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_default_user_and_workdir(): - dockerfile = "FROM node:24" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - assert template._template._instructions[-2]["type"] == InstructionType.USER - assert template._template._instructions[-2]["args"][0] == "user" - assert template._template._instructions[-1]["type"] == InstructionType.WORKDIR - assert template._template._instructions[-1]["args"][0] == "/home/user" - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_custom_user_and_workdir(): - dockerfile = "FROM node:24\nUSER mish\nWORKDIR /home/mish" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - assert template._template._instructions[-2]["type"] == InstructionType.USER - assert template._template._instructions[-2]["args"][0] == "mish" - assert template._template._instructions[-1]["type"] == InstructionType.WORKDIR - assert template._template._instructions[-1]["args"][0] == "/home/mish" - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_multi_source_copy(): - dockerfile = """FROM node:24 -COPY file1.txt file2.txt file3.txt /dest/""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - instructions = template._template._instructions - - copy_instructions = [i for i in instructions if i["type"] == InstructionType.COPY] - - assert len(copy_instructions) == 3 - assert copy_instructions[0]["args"][0] == "file1.txt" - assert copy_instructions[0]["args"][1] == "/dest/" - assert copy_instructions[1]["args"][0] == "file2.txt" - assert copy_instructions[1]["args"][1] == "/dest/" - assert copy_instructions[2]["args"][0] == "file3.txt" - assert copy_instructions[2]["args"][1] == "/dest/" - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_multi_source_copy_chown(): - dockerfile = """FROM node:24 -COPY --chown=myuser:mygroup pkg.json pkg-lock.json /app/""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - instructions = template._template._instructions - - copy_instructions = [i for i in instructions if i["type"] == InstructionType.COPY] - - assert len(copy_instructions) == 2 - assert copy_instructions[0]["args"][0] == "pkg.json" - assert copy_instructions[0]["args"][1] == "/app/" - assert copy_instructions[0]["args"][2] == "myuser:mygroup" - assert copy_instructions[1]["args"][0] == "pkg-lock.json" - assert copy_instructions[1]["args"][1] == "/app/" - assert copy_instructions[1]["args"][2] == "myuser:mygroup" - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_copy_chown(): - dockerfile = """FROM node:24 -COPY --chown=myuser:mygroup app.js /app/ -COPY --chown=anotheruser config.json /config/""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - - instructions = template._template._instructions - - # First COPY instruction (after initial USER root and WORKDIR /) - copy_instruction1 = instructions[2] - assert copy_instruction1["type"] == InstructionType.COPY - assert copy_instruction1["args"][0] == "app.js" - assert copy_instruction1["args"][1] == "/app/" - assert copy_instruction1["args"][2] == "myuser:mygroup" # user from --chown - - # Second COPY instruction - copy_instruction2 = instructions[3] - assert copy_instruction2["type"] == InstructionType.COPY - assert copy_instruction2["args"][0] == "config.json" - assert copy_instruction2["args"][1] == "/config/" - assert ( - copy_instruction2["args"][2] == "anotheruser" - ) # user from --chown (without group) - - -def _instructions(template): - return template._template._instructions - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_quoted_env_and_arg_values(): - dockerfile = """FROM node:24 -ENV A="hello world" B='single quoted' C=plain D=escaped\\ space -ENV LEGACY some value with spaces -ENV MULTI=1 \\ - # comment inside continuation - LINE="two \\ -words" -ARG VERSION=1.0 NAME="with space" EMPTY -ENV REF="$HOME/bin:${PATH}\"""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - envs = [ - i["args"] for i in _instructions(template) if i["type"] == InstructionType.ENV - ] - - assert envs == [ - ["A", "hello world", "B", "single quoted", "C", "plain", "D", "escaped space"], - ["LEGACY", "some value with spaces"], - ["MULTI", "1", "LINE", "two words"], - ["VERSION", "1.0", "NAME", "with space", "EMPTY", ""], - ["REF", "$HOME/bin:${PATH}"], - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_mixed_case_keywords_and_as_alias(): - dockerfile = """from node:24 As Builder -run echo hi -Workdir /app -uSeR node""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - assert template._template._base_image == "node:24" - instructions = _instructions(template) - assert instructions[2]["type"] == InstructionType.RUN - assert instructions[2]["args"] == ["echo hi"] - assert instructions[3]["args"] == ["/app"] - assert instructions[4]["args"] == ["node"] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_preserves_whitespace_in_run_and_uses_shell_form(): - dockerfile = """FROM node:24 -RUN echo "a b" && \\ - echo 'c d' -RUN ["echo", "hello world", "it's"]""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - runs = [ - i["args"][0] - for i in _instructions(template) - if i["type"] == InstructionType.RUN - ] - - assert runs == [ - """echo "a b" && echo 'c d'""", - """echo 'hello world' 'it'"'"'s'""", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_escape_directive(): - dockerfile = """# escape=` -FROM node:24 -RUN echo one ` - && echo two -ENV P="C:\\tools\"""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - instructions = _instructions(template) - assert instructions[2]["args"] == ["echo one && echo two"] - assert instructions[3]["args"] == ["P", "C:\\tools"] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_copy_chmod_and_quoted_paths(): - dockerfile = """FROM node:24 -COPY --chmod=755 --chown=app:app "my file.txt" other.txt /dest/ -COPY ["json file.txt", "/dest/"] -ADD archive.tar.gz /opt/""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - copies = [ - i["args"] for i in _instructions(template) if i["type"] == InstructionType.COPY - ] - - assert copies == [ - ["my file.txt", "/dest/", "app:app", "0755"], - ["other.txt", "/dest/", "app:app", "0755"], - ["json file.txt", "/dest/", "", ""], - ["archive.tar.gz", "/opt/", "", ""], - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_with_heredocs(): - dockerfile = """FROM node:24 -RUN <"$E2B_HEREDOC_DEST"\nkey=$VALUE\nE2B_HEREDOC_CONF""", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_heredoc_runs_as_root(): - dockerfile = """FROM node:24 -USER app -COPY <"$E2B_HEREDOC_DEST"\n' - "E2B_HEREDOC_EOF\nx=1\nE2B_HEREDOC_EOF_", - "root", - ], - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_heredoc_terminator_avoids_the_content(): - dockerfile = """FROM node:24 -COPY <"$E2B_HEREDOC_DEST"\n' - "E2B_HEREDOC_EOF___ E2B_HEREDOC_EOF_\nE2B_HEREDOC_EOF\nE2B_HEREDOC_EOF____", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_heredoc_keeps_docker_substitution_patterns(): - dockerfile = """FROM node:24 -COPY <"$E2B_HEREDOC_DEST"\n' - "${X/old/new} ${X//o/0} ${X/a\\/b/c} ${X/\\`/x} ${Y#\\*} ${Y%%\\}} ${Z:-foo\\}bar} ${Z:-a\\\\b}\nE2B_HEREDOC_EOF", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_chmod_resolves_arg_and_env_values(): - dockerfile = """FROM node:24 -ARG MODE=440 -ENV FULL=0${MODE} -COPY --chmod=$MODE a /a -COPY --chmod=${FULL} b /b -COPY --chmod=${UNSET:-755} c /c""" - - template = AsyncTemplate().from_dockerfile(dockerfile) - copies = [ - i["args"] for i in _instructions(template) if i["type"] == InstructionType.COPY - ] - - assert copies == [ - ["a", "/a", "", "0440"], - ["b", "/b", "", "0440"], - ["c", "/c", "", "0755"], - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_heredoc_with_shell_unsafe_name(): - dockerfile = """FROM node:24 -COPY <"$E2B_HEREDOC_DEST"\n' - "hi\nE2B_HEREDOC_FOO_BAR", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_heredoc_into_directory_expands_only_variables(): - dockerfile = """FROM node:24 -COPY <"$E2B_HEREDOC_DEST"\n' - "run \\`npm start\\` and \\$(id) for $USER, \\$(literal) and ${HOME:-\\$(x)}\nE2B_HEREDOC_EOF", - "E2B_HEREDOC_DEST=.\n" - 'if [ -d "$E2B_HEREDOC_DEST" ]; then E2B_HEREDOC_DEST="$E2B_HEREDOC_DEST"/EOF; fi\n' - 'mkdir -p "$(dirname "$E2B_HEREDOC_DEST")" && cat <"$E2B_HEREDOC_DEST"\n' - "x\nE2B_HEREDOC_EOF", - ] - - -@pytest.mark.skip_debug() -async def test_from_dockerfile_copy_with_literal_paths_and_zero_mode(): - dockerfile = """FROM node:24 -COPY "<= 1 assert any(e.name == "file.txt" for e in entries) - @pytest.mark.skip(reason="depth option not yet supported") - async def test_list_with_depth(self, async_volume: AsyncVolume): - await async_volume.make_dir("/deep/nested/structure", force=True) - await async_volume.write_file("/deep/nested/structure/file.txt", "Content") - - entries = await async_volume.list("/deep", depth=2) - - assert len(entries) > 0 - async def test_list_nonexistent_raises(self, async_volume: AsyncVolume): with pytest.raises(NotFoundException): await async_volume.list("/non-existent") @@ -260,15 +241,6 @@ async def test_remove_directory(self, async_volume: AsyncVolume): assert await async_volume.exists(path) is False - async def test_remove_directory_recursively(self, async_volume: AsyncVolume): - dir_path = "/recursive-dir" - await async_volume.make_dir(f"{dir_path}/nested", force=True) - await async_volume.write_file(f"{dir_path}/nested/file.txt", "Content") - - await async_volume.remove(dir_path) - - assert await async_volume.exists(dir_path) is False - async def test_remove_nonexistent_raises(self, async_volume: AsyncVolume): with pytest.raises(NotFoundException): await async_volume.remove("/non-existent.txt") diff --git a/packages/python-sdk/tests/async/volume_async/test_volume.py b/packages/python-sdk/tests/async/volume_async/test_volume.py index 317beab725..73cc864a1c 100644 --- a/packages/python-sdk/tests/async/volume_async/test_volume.py +++ b/packages/python-sdk/tests/async/volume_async/test_volume.py @@ -203,26 +203,3 @@ async def test_connect_propagates_byoc_domain(): connected = await AsyncVolume.connect(created.volume_id) assert connected._domain == byoc_domain assert connected._get_volume_config().domain == byoc_domain - - -async def test_volume_full_lifecycle(): - # Create - vol = await AsyncVolume.create("lifecycle-vol") - assert vol.name == "lifecycle-vol" - - # Get info - info = await AsyncVolume.get_info(vol.volume_id) - assert info.name == "lifecycle-vol" - - # List - volumes = await AsyncVolume.list() - assert len(volumes) == 1 - assert volumes[0].volume_id == vol.volume_id - - # Destroy - destroyed = await AsyncVolume.destroy(vol.volume_id) - assert destroyed is True - - # List again - volumes = await AsyncVolume.list() - assert len(volumes) == 0 diff --git a/packages/python-sdk/tests/shared/sandbox/test_dns_servers.py b/packages/python-sdk/tests/shared/sandbox/test_dns_servers.py index 571385558b..0a3885f2ff 100644 --- a/packages/python-sdk/tests/shared/sandbox/test_dns_servers.py +++ b/packages/python-sdk/tests/shared/sandbox/test_dns_servers.py @@ -50,11 +50,6 @@ def test_create_treats_none_as_not_set(): assert "dnsServers" not in wire -def test_create_leaves_out_an_empty_network_config(): - # Both create paths skip the network field when this is empty. - assert not build_network_config({}) - - @pytest.mark.parametrize("dns_servers", [[], ["1.1.1.1"]]) def test_update_sends_dns_servers(dns_servers): network: SandboxNetworkUpdate = { diff --git a/packages/python-sdk/tests/shared/secret/test_fill.py b/packages/python-sdk/tests/shared/secret/test_fill.py new file mode 100644 index 0000000000..a228440c8a --- /dev/null +++ b/packages/python-sdk/tests/shared/secret/test_fill.py @@ -0,0 +1,14 @@ +import pytest + +from e2b import Secret +from e2b.exceptions import InvalidArgumentException + + +def test_fill(): + assert Secret.fill("openai-api-key") == "${e2b.secrets.openai-api-key}" + + +@pytest.mark.parametrize("name", ["", "{name", "name}", "name\n", "name\x00"]) +def test_fill_invalid_name_raises(name): + with pytest.raises(InvalidArgumentException): + Secret.fill(name) diff --git a/packages/python-sdk/tests/sync/template_sync/methods/test_from_dockerfile.py b/packages/python-sdk/tests/shared/template/methods/test_from_dockerfile.py similarity index 100% rename from packages/python-sdk/tests/sync/template_sync/methods/test_from_dockerfile.py rename to packages/python-sdk/tests/shared/template/methods/test_from_dockerfile.py diff --git a/packages/python-sdk/tests/sync/template_sync/methods/test_to_dockerfile.py b/packages/python-sdk/tests/shared/template/methods/test_to_dockerfile.py similarity index 100% rename from packages/python-sdk/tests/sync/template_sync/methods/test_to_dockerfile.py rename to packages/python-sdk/tests/shared/template/methods/test_to_dockerfile.py diff --git a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_env_vars.py b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_env_vars.py index a9752de248..18a6749368 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_env_vars.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_env_vars.py @@ -3,11 +3,6 @@ from e2b import Sandbox -def test_command_envs(sandbox: Sandbox): - cmd = sandbox.commands.run("echo $FOO", envs={"FOO": "bar"}) - assert cmd.stdout.strip() == "bar" - - @pytest.mark.skip_debug() def test_sandbox_envs(sandbox_factory): sbx = sandbox_factory(envs={"FOO": "bar"}) diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py index e808ac4c9d..fa822b4464 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_watch.py @@ -183,10 +183,3 @@ def test_watch_file(sandbox: Sandbox): with pytest.raises(SandboxException): sandbox.files.watch_dir(filename) - - -def test_watch_file_with_secured_envd(sandbox_factory): - sbx = sandbox_factory(timeout=30) - - sbx.files.watch_dir("/home/user/") - sbx.files.write("test_watch.txt", "This file will be watched.") diff --git a/packages/python-sdk/tests/sync/sandbox_sync/files/test_write.py b/packages/python-sdk/tests/sync/sandbox_sync/files/test_write.py index 066499dfe5..8b51f18069 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/files/test_write.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/files/test_write.py @@ -1,5 +1,4 @@ import io -import uuid import pytest @@ -128,24 +127,6 @@ def test_write_to_non_existing_directory(sandbox, debug): sandbox.files.remove(filename) -def test_write_with_secured_envd(sandbox_factory): - filename = f"non_existing_dir_{uuid.uuid4()}/test_write.txt" - content = "This should succeed too." - - sbx = sandbox_factory(timeout=30) - assert sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - sbx.files.write(filename, content) - - exists = sbx.files.exists(filename) - assert exists - - read_content = sbx.files.read(filename) - assert read_content == content - - def test_write_files_with_different_data_types(sandbox, debug): text_data = "Text string data" bytes_data = b"Bytes data" diff --git a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py b/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py deleted file mode 100644 index 15c5df0344..0000000000 --- a/packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py +++ /dev/null @@ -1,9 +0,0 @@ -from e2b import Sandbox -from e2b.sandbox.commands.command_handle import PtySize - - -def test_send_input(sandbox: Sandbox): - terminal = sandbox.pty.create(PtySize(cols=80, rows=24)) - sandbox.pty.send_stdin(terminal.pid, b"exit\n") - result = terminal.wait() - assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_network.py b/packages/python-sdk/tests/sync/sandbox_sync/test_network.py index e372f5b8d5..f5c820ea86 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_network.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_network.py @@ -1,6 +1,5 @@ import json import time -from unittest.mock import Mock import httpx @@ -37,15 +36,6 @@ def wait_for_status( raise last_transport_error -def test_wait_for_status_retries_transport_errors(): - response = httpx.Response(200) - client = Mock() - client.get.side_effect = [httpx.ConnectError("not ready"), response] - - assert wait_for_status(client, "https://sandbox.test", 200) is response - assert client.get.call_count == 2 - - @pytest.mark.skip_debug() def test_allow_specific_ip_with_deny_all(sandbox_factory): """Test that sandbox with denyOut all and allowOut creates a whitelist.""" diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_secure.py b/packages/python-sdk/tests/sync/sandbox_sync/test_secure.py deleted file mode 100644 index be9097582c..0000000000 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_secure.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest - -from e2b import Sandbox - - -@pytest.mark.skip_debug() -def test_start_secured(sandbox_factory): - sbx = sandbox_factory(timeout=5) - - assert sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - -@pytest.mark.skip_debug() -def test_connect_to_secured(sandbox_factory): - sbx = sandbox_factory(timeout=5) - - assert sbx.is_running() - assert sbx._envd_version is not None - assert sbx._envd_access_token is not None - - sbx_connection = Sandbox.connect(sbx.sandbox_id) - assert sbx_connection.is_running() - assert sbx_connection._envd_version is not None - assert sbx_connection._envd_access_token is not None diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_sync_client_lifecycle.py b/packages/python-sdk/tests/sync/sandbox_sync/test_sync_client_lifecycle.py deleted file mode 100644 index 5325994bfc..0000000000 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_sync_client_lifecycle.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Client lifecycle in the sync sandbox modules: the httpx `envd_api` -clients and the connectrpc RPC clients are both cheap stateless wrappers -over shared, process-global pyqwest transports — built once per sandbox and -shared across the modules and across threads.""" - -from concurrent.futures import ThreadPoolExecutor -from unittest.mock import Mock, sentinel - -import httpx -from packaging.version import Version - -from e2b.connection_config import ConnectionConfig -from e2b.sandbox_sync.filesystem import filesystem as filesystem_sync -from e2b.sandbox_sync.filesystem.filesystem import Filesystem -import e2b.sandbox_sync.main as sandbox_sync_main - - -ENVD_API_URL = "https://sandbox.e2b.app" -ENVD_VERSION = Version("0.6.2") - - -def run_in_worker_thread(fn): - with ThreadPoolExecutor(max_workers=1) as executor: - return executor.submit(fn).result() - - -def test_sync_sandbox_shares_one_envd_api_across_modules(monkeypatch, test_api_key): - config = ConnectionConfig(api_key=test_api_key) - main_api = Mock(spec=httpx.Client) - received = {} - - def record(name): - def factory(*args, **kwargs): - received[name] = args[-1] - return object() - - return factory - - monkeypatch.setattr( - sandbox_sync_main, "get_envd_api", lambda *_args, **_kwargs: main_api - ) - for module in ("Filesystem", "Commands", "Pty"): - monkeypatch.setattr(sandbox_sync_main, module, record(module)) - monkeypatch.setattr(sandbox_sync_main, "Git", lambda *args, **kwargs: object()) - - sandbox = sandbox_sync_main.Sandbox( - sandbox_id="sbx-test", - sandbox_domain="e2b.app", - envd_version=ENVD_VERSION, - envd_access_token="tok", - traffic_access_token="tok", - connection_config=config, - ) - - assert sandbox._envd_api is main_api - assert received == {"Filesystem": main_api, "Commands": main_api, "Pty": main_api} - assert not hasattr(sandbox, "_transport") - - -def test_sync_filesystem_clients_are_shared_across_threads(monkeypatch, test_api_key): - config = ConnectionConfig(api_key=test_api_key) - shared_api = Mock(spec=httpx.Client) - streaming_api = Mock(spec=httpx.Client) - shared_rpc = sentinel.filesystem_rpc - - monkeypatch.setattr( - filesystem_sync, - "get_envd_api", - lambda *_args, **_kwargs: streaming_api, - ) - monkeypatch.setattr( - filesystem_sync, - "create_rpc_client", - lambda *_args, **_kwargs: shared_rpc, - ) - - fs = Filesystem( - ENVD_API_URL, - ENVD_VERSION, - config, - shared_api, - ) - - assert fs._envd_api is shared_api - assert fs._envd_api_streaming is streaming_api - assert fs._rpc is shared_rpc - - worker_api, worker_streaming, worker_rpc = run_in_worker_thread( - lambda: (fs._envd_api, fs._envd_api_streaming, fs._rpc) - ) - assert worker_api is shared_api - assert worker_streaming is streaming_api - assert worker_rpc is shared_rpc diff --git a/packages/python-sdk/tests/sync/secret_sync/test_secret.py b/packages/python-sdk/tests/sync/secret_sync/test_secret.py index 8b4b939985..572b24719d 100644 --- a/packages/python-sdk/tests/sync/secret_sync/test_secret.py +++ b/packages/python-sdk/tests/sync/secret_sync/test_secret.py @@ -8,7 +8,7 @@ from e2b.api.client.models.secret import Secret as SecretModel from e2b.api.client.models.secret_metadata import SecretMetadata from e2b.api.client.types import UNSET, Response -from e2b.exceptions import InvalidArgumentException, SecretNotFoundException +from e2b.exceptions import SecretNotFoundException import e2b.api.client.api.secrets.delete_secrets_secret_id as delete_secret_mod import e2b.api.client.api.secrets.get_secrets as get_secrets_mod import e2b.api.client.api.secrets.get_secrets_secret_id as get_secret_mod @@ -192,13 +192,3 @@ def test_destroy_secret(): def test_destroy_nonexistent_secret(): assert Secret.destroy("missing") is False - - -def test_fill(): - assert Secret.fill("openai-api-key") == "${e2b.secrets.openai-api-key}" - - -@pytest.mark.parametrize("name", ["", "{name", "name}", "name\n", "name\x00"]) -def test_fill_invalid_name_raises(name): - with pytest.raises(InvalidArgumentException): - Secret.fill(name) diff --git a/packages/python-sdk/tests/sync/volume_sync/test_file.py b/packages/python-sdk/tests/sync/volume_sync/test_file.py index eb2dfa0c0a..51968bf8c6 100644 --- a/packages/python-sdk/tests/sync/volume_sync/test_file.py +++ b/packages/python-sdk/tests/sync/volume_sync/test_file.py @@ -18,24 +18,14 @@ def test_write_and_read_text_file(self, volume: Volume): assert read_content == content def test_write_and_read_bytes(self, volume: Volume): - path = "/test-bytes.txt" - content = "Test bytes content" - content_bytes = content.encode("utf-8") + path = "/test-bytes.bin" + content_bytes = bytes([0, 1, 2, 3, 4]) volume.write_file(path, content_bytes) read_bytes = volume.read_file(path, format="bytes") assert read_bytes == content_bytes - def test_write_and_read_binary_data(self, volume: Volume): - path = "/binary.bin" - binary_data = bytes([0, 1, 2, 3, 4]) - - volume.write_file(path, binary_data) - read_bytes = volume.read_file(path, format="bytes") - - assert read_bytes == binary_data - def test_write_and_read_stream(self, volume: Volume): path = "/test-stream.txt" content = "Test stream content" @@ -216,15 +206,6 @@ def test_list_nested_directory(self, volume: Volume): assert len(entries) >= 1 assert any(e.name == "file.txt" for e in entries) - @pytest.mark.skip(reason="depth option not yet supported") - def test_list_with_depth(self, volume: Volume): - volume.make_dir("/deep/nested/structure", force=True) - volume.write_file("/deep/nested/structure/file.txt", "Content") - - entries = volume.list("/deep", depth=2) - - assert len(entries) > 0 - def test_list_nonexistent_raises(self, volume: Volume): with pytest.raises(NotFoundException): volume.list("/non-existent") @@ -247,15 +228,6 @@ def test_remove_directory(self, volume: Volume): assert volume.exists(path) is False - def test_remove_directory_recursively(self, volume: Volume): - dir_path = "/recursive-dir" - volume.make_dir(f"{dir_path}/nested", force=True) - volume.write_file(f"{dir_path}/nested/file.txt", "Content") - - volume.remove(dir_path) - - assert volume.exists(dir_path) is False - def test_remove_nonexistent_raises(self, volume: Volume): with pytest.raises(NotFoundException): volume.remove("/non-existent.txt") diff --git a/packages/python-sdk/tests/sync/volume_sync/test_volume.py b/packages/python-sdk/tests/sync/volume_sync/test_volume.py index 453eb90413..b51e3cefee 100644 --- a/packages/python-sdk/tests/sync/volume_sync/test_volume.py +++ b/packages/python-sdk/tests/sync/volume_sync/test_volume.py @@ -201,26 +201,3 @@ def test_connect_propagates_byoc_domain(): connected = Volume.connect(created.volume_id) assert connected._domain == byoc_domain assert connected._get_volume_config().domain == byoc_domain - - -def test_volume_full_lifecycle(): - # Create - vol = Volume.create("lifecycle-vol") - assert vol.name == "lifecycle-vol" - - # Get info - info = Volume.get_info(vol.volume_id) - assert info.name == "lifecycle-vol" - - # List - volumes = Volume.list() - assert len(volumes) == 1 - assert volumes[0].volume_id == vol.volume_id - - # Destroy - destroyed = Volume.destroy(vol.volume_id) - assert destroyed is True - - # List again - volumes = Volume.list() - assert len(volumes) == 0 diff --git a/packages/python-sdk/tests/test_api_client_transport.py b/packages/python-sdk/tests/test_api_client_transport.py index c29d0cce78..bcd93f9c9d 100644 --- a/packages/python-sdk/tests/test_api_client_transport.py +++ b/packages/python-sdk/tests/test_api_client_transport.py @@ -34,11 +34,6 @@ from e2b.connection_config import READ_TIMEOUT, ConnectionConfig -def run_in_worker_thread(fn): - with ThreadPoolExecutor(max_workers=1) as executor: - return executor.submit(fn).result() - - def test_sync_api_client_proxy_uses_explicit_transport(test_api_key): reset_transport_caches() config = ConnectionConfig( @@ -60,32 +55,6 @@ def test_sync_api_client_proxy_uses_explicit_transport(test_api_key): reset_transport_caches() -def test_sync_retry_policy_wraps_but_does_not_split_cached_transport(test_api_key): - reset_transport_caches() - default_client = get_sync_api_client(ConnectionConfig(api_key=test_api_key)) - retrying_client = get_sync_api_client( - ConnectionConfig(api_key=test_api_key, retries=2) - ) - default_httpx = default_client.get_httpx_client() - retrying_httpx = retrying_client.get_httpx_client() - raw_transport = get_sync_transport( - ConnectionConfig(api_key=test_api_key, retries=1) - ) - - try: - assert isinstance(default_httpx._transport, RetryableTransport) - assert default_httpx._transport.retries == 3 - assert isinstance(retrying_httpx._transport, RetryableTransport) - assert retrying_httpx._transport.retries == 2 - assert retrying_httpx._transport.transport is default_httpx._transport.transport - assert isinstance(raw_transport, PyqwestTransport) - assert raw_transport is default_httpx._transport.transport - finally: - retrying_httpx.close() - default_httpx.close() - reset_transport_caches() - - def test_sync_get_transport_keyed_by_proxy(test_api_key): reset_transport_caches() proxied_config = ConnectionConfig( @@ -266,22 +235,6 @@ def test_sync_api_client_request_timeout_zero_disables_timeout(test_api_key): reset_transport_caches() -def test_sync_generic_transport_separates_streaming_read_timeout(test_api_key): - reset_transport_caches() - config = ConnectionConfig(api_key=test_api_key) - - try: - api_transport = get_sync_transport(config) - streaming_transport = get_sync_transport(config, for_streaming=True) - - assert isinstance(api_transport, PyqwestTransport) - assert api_transport is get_sync_transport(config, for_streaming=False) - assert streaming_transport is not api_transport - assert get_sync_transport(config, for_streaming=True) is streaming_transport - finally: - reset_transport_caches() - - def test_sync_envd_api_client_wiring(test_api_key): reset_transport_caches() config = ConnectionConfig(api_key=test_api_key) @@ -301,25 +254,6 @@ def test_sync_envd_api_client_wiring(test_api_key): reset_transport_caches() -def test_sync_api_client_is_shared_across_threads(test_api_key): - # httpx.Client is thread-safe and the pyqwest transport underneath is - # too, so a single client (and its pool) serves all threads — the - # per-thread client caching this replaced is gone. - reset_transport_caches() - config = ConnectionConfig(api_key=test_api_key) - api_client = get_sync_api_client(config) - - try: - main_client = api_client.get_httpx_client() - worker_client = run_in_worker_thread(api_client.get_httpx_client) - - assert api_client.get_httpx_client() is main_client - assert worker_client is main_client - finally: - main_client.close() - reset_transport_caches() - - @pytest.mark.asyncio async def test_async_api_client_proxy_uses_explicit_transport(test_api_key): reset_transport_caches() @@ -487,23 +421,6 @@ async def get_client(): reset_transport_caches() -@pytest.mark.asyncio -async def test_async_generic_transport_separates_streaming_read_timeout(test_api_key): - reset_transport_caches() - config = ConnectionConfig(api_key=test_api_key) - - try: - api_transport = get_async_transport(config) - streaming_transport = get_async_transport(config, for_streaming=True) - - assert isinstance(api_transport, AsyncPyqwestTransport) - assert api_transport is get_async_transport(config, for_streaming=False) - assert streaming_transport is not api_transport - assert get_async_transport(config, for_streaming=True) is streaming_transport - finally: - reset_transport_caches() - - @pytest.mark.asyncio async def test_async_envd_api_client_wiring(test_api_key): reset_transport_caches() diff --git a/packages/python-sdk/tests/test_client.py b/packages/python-sdk/tests/test_client.py index 8f4d164116..5a0234493a 100644 --- a/packages/python-sdk/tests/test_client.py +++ b/packages/python-sdk/tests/test_client.py @@ -154,16 +154,6 @@ def test_per_call_params_override_the_client(api_server): assert sandbox.connection_config.domain == DOMAIN_B -def test_client_class_can_be_rebound(api_server): - client = E2B(api_key=API_KEY_A, domain=DOMAIN_A, api_url=api_server) - - S = client.Sandbox - sandbox = S.create() - - assert api_keys() == [API_KEY_A] - assert sandbox.connection_config.api_key == API_KEY_A - - def test_per_call_params_set_to_none_keep_the_client_config(api_server): client = E2B(api_key=API_KEY_A, domain=DOMAIN_A, api_url=api_server) diff --git a/packages/python-sdk/tests/test_connection_config.py b/packages/python-sdk/tests/test_connection_config.py index b9ff76c157..654e789fe3 100644 --- a/packages/python-sdk/tests/test_connection_config.py +++ b/packages/python-sdk/tests/test_connection_config.py @@ -138,15 +138,6 @@ def test_sandbox_direct_url_with_port_uses_per_sandbox_host(): ) -def test_sandbox_url_without_port_uses_envd_port(): - config = ConnectionConfig(domain="example.dev") - - assert ( - config.get_sandbox_url("sandbox-id", "example.dev") - == "https://49983-sandbox-id.example.dev" - ) - - def test_sandbox_direct_url_uses_explicit_url_first(): config = ConnectionConfig(sandbox_url="https://sandbox.example.com") diff --git a/packages/python-sdk/tests/test_envd_interceptors.py b/packages/python-sdk/tests/test_envd_interceptors.py index 0cd9a099ed..2035917026 100644 --- a/packages/python-sdk/tests/test_envd_interceptors.py +++ b/packages/python-sdk/tests/test_envd_interceptors.py @@ -8,11 +8,9 @@ from functools import partial from connectrpc._interceptor_async import ( - MetadataInterceptor, resolve_interceptors as resolve_interceptors_async, ) from connectrpc._interceptor_sync import ( - MetadataInterceptorSync, resolve_interceptors as resolve_interceptors_sync, ) from unittest.mock import MagicMock @@ -50,15 +48,6 @@ def _compose_async(interceptors, base): return execute -def test_headers_interceptor_is_not_a_metadata_interceptor(): - # connectrpc wraps metadata interceptors around server streams in a - # generator that doesn't propagate close()/aclose() to the inner stream — - # the headers interceptor must resolve as a direct interceptor instead. - interceptor = DefaultHeadersInterceptor({}) - assert not isinstance(interceptor, MetadataInterceptor) - assert not isinstance(interceptor, MetadataInterceptorSync) - - def test_headers_interceptor_applies_defaults_and_keeps_per_call_headers(): interceptor = DefaultHeadersInterceptor({"User-Agent": "sdk", "X-Token": "t"}) ctx = _ctx() diff --git a/packages/python-sdk/tests/test_envd_rpc_exception.py b/packages/python-sdk/tests/test_envd_rpc_exception.py index 9208e9ba64..bfdd661872 100644 --- a/packages/python-sdk/tests/test_envd_rpc_exception.py +++ b/packages/python-sdk/tests/test_envd_rpc_exception.py @@ -50,6 +50,7 @@ def test_maps_resource_exhausted_to_rate_limit(): def test_maps_unavailable_with_port_closed_to_unreachable(): err = handle_rpc_exception(ConnectError(Code.UNAVAILABLE, PORT_CLOSED)) assert isinstance(err, SandboxUnreachableException) + assert not isinstance(err, SandboxNotRunningException) assert "could not be reached" in str(err) @@ -190,12 +191,6 @@ def test_unavailable_sandbox_not_found_returns_not_running(): assert "The sandbox was not found" in str(err) -def test_unavailable_port_closed_is_not_not_running(): - err = handle_rpc_exception(ConnectError(Code.UNAVAILABLE, PORT_CLOSED)) - assert isinstance(err, SandboxUnreachableException) - assert not isinstance(err, SandboxNotRunningException) - - def test_unavailable_sandbox_not_found_does_not_probe(): probed = [] diff --git a/packages/python-sdk/tests/test_logging_option.py b/packages/python-sdk/tests/test_logging_option.py index c105920757..ed9ccad00c 100644 --- a/packages/python-sdk/tests/test_logging_option.py +++ b/packages/python-sdk/tests/test_logging_option.py @@ -1,15 +1,13 @@ -import inspect import logging from types import SimpleNamespace -from e2b import AsyncSandbox, ConnectionConfig, Sandbox +from e2b import ConnectionConfig from e2b.envd.interceptors import LoggingInterceptor, build_interceptors from e2b.api import ( ApiClient, make_async_logging_event_hooks, make_logging_event_hooks, ) -from e2b.connection_config import ApiParams from e2b.volume.connection_config import VolumeConnectionConfig @@ -24,13 +22,6 @@ def test_connection_config_logger_defaults_to_none(): assert config.logger is None -def test_logger_is_not_a_public_per_request_api_param(): - # Matching the JS SDK, `logger` is a construction-time option (Sandbox.create - # / connect), not a public per-request ApiParams field that control-plane - # methods like kill/list/get_info accept from the caller. - assert "logger" not in ApiParams.__annotations__ - - def test_get_api_params_propagates_stored_logger(): # Instance control-plane methods (kill, pause, set_timeout, get_info, # connect) rebuild a throwaway ConnectionConfig from these params, so the @@ -44,17 +35,6 @@ def test_get_api_params_propagates_stored_logger(): assert no_logger.get_api_params()["logger"] is None -def test_logger_is_accepted_on_create_and_connect(): - for cls in (Sandbox, AsyncSandbox): - assert "logger" in inspect.signature(cls.create).parameters - # `logger` is a construction option, so it is accepted by the static - # `Sandbox.connect(sandbox_id, ...)` form (which builds a fresh instance) - # but not by instance `sandbox.connect()`, where the already-built clients - # cannot adopt a new logger. - assert "logger" not in inspect.signature(Sandbox.connect).parameters - assert "logger" not in inspect.signature(AsyncSandbox.connect).parameters - - def test_volume_connection_config_stores_and_round_trips_logger(): custom = logging.getLogger("test.volume") config = VolumeConnectionConfig(token="token", logger=custom) diff --git a/packages/python-sdk/tests/test_rate_limit_retry_transport.py b/packages/python-sdk/tests/test_rate_limit_retry_transport.py index aa3a7786d0..859c2736ba 100644 --- a/packages/python-sdk/tests/test_rate_limit_retry_transport.py +++ b/packages/python-sdk/tests/test_rate_limit_retry_transport.py @@ -100,11 +100,14 @@ def test_retries_rate_limit_after_server_delay_and_replays_body(): assert inner.responses[0].is_closed -def test_exhaustion_returns_final_rate_limit_response(): - inner = FakeTransport([429, 429, 429], should_retry="true") +@pytest.mark.parametrize("status", [429, 503]) +def test_exhaustion_returns_final_retryable_response(status): + inner = FakeTransport( + [status, status, status], retry_after=None, should_retry="true" + ) response = RetryableTransport( - inner, retries=2, sleep=lambda _: None + inner, retries=2, sleep=lambda _: None, random_=lambda: 0.0 ).handle_request(httpx.Request("GET", "https://api.test")) assert response is inner.responses[-1] @@ -263,32 +266,6 @@ def test_retries_post_500_when_marked_by_server(): assert len(inner.requests) == 2 -def test_retries_post_503_when_marked_by_server(): - inner = FakeTransport([503, 200], retry_after=None, should_retry="true") - - response = RetryableTransport( - inner, retries=3, sleep=lambda _: None, monotonic=lambda: 0.0 - ).handle_request( - httpx.Request("POST", "https://api.test/v2/sandboxes", content=b"p") - ) - - assert response.status_code == 200 - assert len(inner.requests) == 2 - - -def test_retries_post_502_without_should_retry_header(): - inner = FakeTransport([502, 200], retry_after="0", should_retry=None) - - response = RetryableTransport( - inner, retries=1, sleep=lambda _: None, monotonic=lambda: 0.0 - ).handle_request( - httpx.Request("POST", "https://api.test/v2/sandboxes", content=b"payload") - ) - - assert response.status_code == 200 - assert len(inner.requests) == 2 - - @pytest.mark.parametrize("should_retry", ["TRUE", " true "]) def test_should_retry_header_is_case_and_whitespace_insensitive(should_retry): inner = FakeTransport([503, 200], should_retry=should_retry) @@ -336,18 +313,6 @@ def random_(): assert sleeps == [3] -def test_unavailable_status_exhaustion_returns_final_response(): - inner = FakeTransport([503, 503, 503], retry_after=None, should_retry="true") - - response = RetryableTransport( - inner, retries=2, sleep=lambda _: None, random_=lambda: 0.0 - ).handle_request(httpx.Request("GET", "https://api.test")) - - assert response is inner.responses[-1] - assert len(inner.requests) == 3 - assert response.content == b"response" - - def test_backoff_exceeding_timeout_is_propagated_as_is(): inner = FakeTransport([502], retry_after=None, should_retry="true") sleeps = [] @@ -415,34 +380,6 @@ def sleep(delay): ).handle_request(request) -def test_retry_uses_only_the_remaining_request_timeout(): - clock = FakeClock() - - class AdvancingTransport(FakeTransport): - def handle_request(self, request): - response = super().handle_request(request) - if len(self.requests) == 1: - clock.now = 2.0 - return response - - inner = AdvancingTransport([429, 200], should_retry="true") - request = httpx.Request( - "GET", - "https://api.test", - extensions={"timeout": {"connect": 3.0, "read": 3.0}}, - ) - - response = RetryableTransport( - inner, retries=1, sleep=clock.sleep, monotonic=clock.monotonic - ).handle_request(request) - - assert response.status_code == 200 - assert inner.requests[1].extensions["timeout"] == { - "connect": 1.0, - "read": 1.0, - } - - def test_retry_preserves_remaining_budget_for_each_timeout_phase(): clock = FakeClock() @@ -462,10 +399,11 @@ def handle_request(self, request): }, ) - RetryableTransport( + response = RetryableTransport( inner, retries=1, sleep=clock.sleep, monotonic=clock.monotonic ).handle_request(request) + assert response.status_code == 200 assert inner.requests[1].extensions["timeout"] == { "connect": 1.0, "read": 1.0, @@ -661,26 +599,8 @@ async def sleep(delay): @pytest.mark.asyncio -@pytest.mark.parametrize("status", [500, 503]) -async def test_async_retries_post_when_marked_by_server(status): - inner = FakeAsyncTransport([status, 200], retry_after=None, should_retry="true") - - async def sleep(_): - pass - - response = await AsyncRetryableTransport( - inner, retries=1, sleep=sleep, monotonic=lambda: 0.0 - ).handle_async_request( - httpx.Request("POST", "https://api.test/v2/sandboxes", content=b"payload") - ) - - assert response.status_code == 200 - assert len(inner.requests) == 2 - - -@pytest.mark.asyncio -async def test_async_retries_post_502_without_should_retry_header(): - inner = FakeAsyncTransport([502, 200], retry_after="0", should_retry=None) +async def test_async_retries_post_500_when_marked_by_server(): + inner = FakeAsyncTransport([500, 200], retry_after=None, should_retry="true") async def sleep(_): pass @@ -807,39 +727,6 @@ async def sleep(delay): assert sleeps == [] -@pytest.mark.asyncio -async def test_async_retry_uses_only_the_remaining_request_timeout(): - clock = FakeClock() - - class AdvancingTransport(FakeAsyncTransport): - async def handle_async_request(self, request): - response = await super().handle_async_request(request) - if len(self.requests) == 1: - clock.now = 2.0 - return response - - inner = AdvancingTransport([429, 200], should_retry="true") - - async def sleep(delay): - clock.sleep(delay) - - response = await AsyncRetryableTransport( - inner, retries=1, sleep=sleep, monotonic=clock.monotonic - ).handle_async_request( - httpx.Request( - "GET", - "https://api.test", - extensions={"timeout": {"connect": 3.0, "read": 3.0}}, - ) - ) - - assert response.status_code == 200 - assert inner.requests[1].extensions["timeout"] == { - "connect": 1.0, - "read": 1.0, - } - - @pytest.mark.asyncio async def test_async_retry_preserves_remaining_budget_for_each_timeout_phase(): clock = FakeClock() @@ -856,7 +743,7 @@ async def handle_async_request(self, request): async def sleep(delay): clock.sleep(delay) - await AsyncRetryableTransport( + response = await AsyncRetryableTransport( inner, retries=1, sleep=sleep, monotonic=clock.monotonic ).handle_async_request( httpx.Request( @@ -873,6 +760,7 @@ async def sleep(delay): ) ) + assert response.status_code == 200 assert inner.requests[1].extensions["timeout"] == { "connect": 1.0, "read": 1.0, diff --git a/packages/python-sdk/tests/test_retry_stream_buffering.py b/packages/python-sdk/tests/test_retry_stream_buffering.py index f241cfd948..40b4113580 100644 --- a/packages/python-sdk/tests/test_retry_stream_buffering.py +++ b/packages/python-sdk/tests/test_retry_stream_buffering.py @@ -21,7 +21,6 @@ import pytest from pyqwest import Request, Response, SyncRequest, SyncResponse from pyqwest.httpx import AsyncPyqwestTransport, PyqwestTransport -from pyqwest.middleware.retry import RetryMode from e2b.api.client_async import ConnectionRetryTransport from e2b.api.client_sync import ( @@ -123,18 +122,6 @@ def _retrying_sync(inner) -> SyncConnectionRetryTransport: ) -@pytest.mark.parametrize( - "transport_cls", - [SyncConnectionRetryTransport, ConnectionRetryTransport], - ids=["sync", "async"], -) -def test_transports_declare_the_policy(transport_cls): - # The inherited hook returns `True` — buffered — so nothing behavioral - # below would fail loudly if the override silently vanished. - assert "should_retry_request" in transport_cls.__dict__ - assert transport_cls(None).should_retry_request(None) is RetryMode.UNBUFFERED - - # A failed connect leaves the body untouched, so the same stream serves the # next attempt in either mode — the retries the SDK's policy is about. diff --git a/packages/python-sdk/tests/test_sandbox_create_idempotency.py b/packages/python-sdk/tests/test_sandbox_create_idempotency.py index 5f11646e52..ffdc1198d4 100644 --- a/packages/python-sdk/tests/test_sandbox_create_idempotency.py +++ b/packages/python-sdk/tests/test_sandbox_create_idempotency.py @@ -111,26 +111,6 @@ def test_sync_create_rejects_invalid_idempotency_keys_before_request( assert requests == [] -def test_sync_create_sends_configured_idempotency_key(monkeypatch): - idempotency_keys = [] - - def get_api_client(config): - return object() - - def create(**kwargs): - idempotency_keys.append(kwargs["idempotency_key"]) - return _response() - - monkeypatch.setattr(sync_sandbox_api, "get_api_client", get_api_client) - monkeypatch.setattr(sync_sandbox_api.post_v2_sandboxes, "sync_detailed", create) - - sync_sandbox_api.SandboxApi._create_sandbox( - "base", None, None, None, None, None, idempotency_key="abc123" - ) - - assert idempotency_keys == ["abc123"] - - def test_sync_client_create_sends_per_call_idempotency_key(monkeypatch): idempotency_keys = [] @@ -217,27 +197,6 @@ async def test_async_create_rejects_invalid_idempotency_keys_before_request( assert requests == [] -@pytest.mark.asyncio -async def test_async_create_sends_configured_idempotency_key(monkeypatch): - idempotency_keys = [] - - def get_api_client(config): - return object() - - async def create(**kwargs): - idempotency_keys.append(kwargs["idempotency_key"]) - return _response() - - monkeypatch.setattr(async_sandbox_api, "get_api_client", get_api_client) - monkeypatch.setattr(async_sandbox_api.post_v2_sandboxes, "asyncio_detailed", create) - - await async_sandbox_api.SandboxApi._create_sandbox( - "base", None, None, None, None, None, idempotency_key="abc123" - ) - - assert idempotency_keys == ["abc123"] - - @pytest.mark.asyncio async def test_async_client_create_sends_per_call_idempotency_key(monkeypatch): idempotency_keys = [] diff --git a/packages/python-sdk/tests/test_volume_client.py b/packages/python-sdk/tests/test_volume_client.py index be3c6405a8..ef6575f5e5 100644 --- a/packages/python-sdk/tests/test_volume_client.py +++ b/packages/python-sdk/tests/test_volume_client.py @@ -6,7 +6,6 @@ import httpx import pytest -from pyqwest.httpx import AsyncPyqwestTransport, PyqwestTransport from transport_caches import reset_transport_caches @@ -116,23 +115,6 @@ async def handler(request: httpx.Request) -> httpx.Response: assert attempts == 1 -def test_sync_transport_is_cached_per_proxy(): - reset_volume_transports() - config = VolumeConnectionConfig(token="vol-token") - proxied = VolumeConnectionConfig(token="vol-token", proxy="http://127.0.0.1:8080") - - try: - transport_a = get_sync_transport(config) - transport_b = get_sync_transport(config) - transport_c = get_sync_transport(proxied) - - assert isinstance(transport_a, PyqwestTransport) - assert transport_a is transport_b - assert transport_a is not transport_c - finally: - reset_volume_transports() - - def test_volume_transports_are_the_shared_sdk_pools(test_api_key): # The volume content API draws from the same pools as the control-plane # REST API and the envd HTTP API — reqwest pools per host, so the volume @@ -196,54 +178,6 @@ def test_volume_transports_follow_the_http_version_option(monkeypatch, test_api_ reset_volume_transports() -def test_sync_transport_is_shared_across_threads(): - # pyqwest transports are thread-safe, so one transport (and its pool) - # serves all threads — the per-thread caching this replaced is gone. - reset_volume_transports() - config = VolumeConnectionConfig(token="vol-token") - - try: - main_transport = get_sync_transport(config) - - result = {} - - def worker(): - result["transport"] = get_sync_transport(config) - - thread = threading.Thread(target=worker) - thread.start() - thread.join() - - assert result["transport"] is main_transport - finally: - reset_volume_transports() - - -def test_async_transport_is_shared_across_loops(): - # pyqwest's I/O runs on its own Rust runtime, so the transport is not - # bound to an event loop — the per-loop caching this replaced is gone. - reset_volume_transports() - config = VolumeConnectionConfig(token="vol-token") - proxied = VolumeConnectionConfig(token="vol-token", proxy="http://127.0.0.1:8080") - - async def get_transports(): - return get_async_transport(config), get_async_transport(config) - - try: - transport_a1, transport_a2 = asyncio.run(get_transports()) - transport_b1, _ = asyncio.run(get_transports()) - proxied_transport = get_async_transport(proxied) - - assert isinstance(transport_a1, AsyncPyqwestTransport) - assert transport_a1 is transport_a2 - assert transport_a1 is transport_b1 - - # Different proxy still gets its own transport. - assert proxied_transport is not transport_a1 - finally: - reset_volume_transports() - - CHUNK = b"x" * 1024 @@ -401,28 +335,6 @@ async def run(): assert asyncio.run(run()) == CHUNK * 2 -def test_stream_transport_is_separate_from_regular_transport(): - # reqwest's read timer keeps running while a request body is sent and - # while waiting for the response head, so the idle read timeout lives on - # a dedicated streaming transport — putting it on the shared one would - # cut off uploads and slow unary responses longer than the idle bound. - reset_volume_transports() - config = VolumeConnectionConfig(token="vol-token") - - try: - regular = get_sync_transport(config) - streaming = get_sync_streaming_transport(config) - assert regular is not streaming - assert get_sync_transport(config) is regular - assert get_sync_streaming_transport(config) is streaming - - async_regular = get_async_transport(config) - async_streaming = get_async_streaming_transport(config) - assert async_regular is not async_streaming - finally: - reset_volume_transports() - - def test_sync_non_stream_read_survives_response_slower_than_idle_timeout( short_read_timeout, ):