From 53ec99f4843a3ea935234a3dbf999579e2072489 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:50:11 +0000 Subject: [PATCH 01/15] test(js-sdk): remove duplicate core unit and list tests Drop the duplicated second block in api/list.test.ts (keeping one static Sandbox.betaPause call), redundant POST retry cases, the bounded RPC dispatcher case, duplicate handleRpcError Unavailable cases (folding their notInstanceOf checks into the surviving tests), the uncleared handshake timer case and the trivial undici load check. Co-Authored-By: mish@e2b.dev --- packages/js-sdk/tests/api/list.test.ts | 220 +----------------- .../js-sdk/tests/connectionConfig.test.ts | 6 - .../js-sdk/tests/envd/handleRpcError.test.ts | 31 +-- packages/js-sdk/tests/envd/http2.test.ts | 22 -- packages/js-sdk/tests/retry.test.ts | 41 ---- packages/js-sdk/tests/undici.test.ts | 13 -- 6 files changed, 3 insertions(+), 330 deletions(-) diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index e184b19eda..644ec9ad55 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.betaPause(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/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')( From afcd76d395c0d35a40e57f61e3dbd9f717cb1000 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:09 +0000 Subject: [PATCH 02/15] test(js-sdk): remove duplicate pty sendInput test Co-Authored-By: mish@e2b.dev --- .../js-sdk/tests/sandbox/pty/sendInput.test.ts | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 packages/js-sdk/tests/sandbox/pty/sendInput.test.ts 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) -}) From b3f8d3b121fd8fbcee92019e5716106ef30cd6b4 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:42 +0000 Subject: [PATCH 03/15] test(js-sdk): remove duplicate sandbox files tests Co-Authored-By: mish@e2b.dev --- .../js-sdk/tests/sandbox/files/info.test.ts | 11 ----- .../js-sdk/tests/sandbox/files/list.test.ts | 46 +------------------ .../tests/sandbox/files/signing.test.ts | 9 ---- .../js-sdk/tests/sandbox/files/write.test.ts | 20 -------- 4 files changed, 1 insertion(+), 85 deletions(-) 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.' From 2af7e53606e0c55a1744620a95a8cfaf621941e3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:49:30 +0000 Subject: [PATCH 04/15] test(js-sdk): remove redundant sandbox signing, iam and config tests Co-Authored-By: mish@e2b.dev --- .../tests/sandbox/configPropagation.test.ts | 8 ------ packages/js-sdk/tests/sandbox/iam.test.ts | 27 ------------------- packages/js-sdk/tests/sandbox/secure.test.ts | 23 ---------------- 3 files changed, 58 deletions(-) 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/iam.test.ts b/packages/js-sdk/tests/sandbox/iam.test.ts index 662a78a465..f9c00a820a 100644 --- a/packages/js-sdk/tests/sandbox/iam.test.ts +++ b/packages/js-sdk/tests/sandbox/iam.test.ts @@ -143,33 +143,6 @@ test('the token map serializes, awaits and coerces like a plain object', async ( expect(await tokens).toBe(tokens) }) -test.for(RUNTIME_PROBED_PROPS)( - 'reading the unregistered token %s throws instead of resolving', - (prop: string) => { - const tokens = iamTokenPlaceholders(['aws'], { validate: true }) - - expect(() => `${tokens[prop]}`).toThrowError( - `iam token '${prop}', which is not registered` - ) - expect(() => String(tokens[prop])).toThrowError(InvalidArgumentError) - // Assigned straight through, a header value is serialized, not coerced. - expect(() => JSON.stringify({ header: tokens[prop] })).toThrowError( - InvalidArgumentError - ) - } -) - -test('the runtime-probed valueOf answers with the guarded map', () => { - // It stays callable for `String(iam.tokens)`, so it must not hand out the - // unguarded record. - const tokens = iamTokenPlaceholders(['aws'], { validate: true }) - - expect(tokens.valueOf().aws).toBe('${e2b.identity.tokens.aws}') - expect(() => `${tokens.valueOf().gcp}`).toThrowError( - /iam token 'gcp'.*Registered tokens: 'aws'/s - ) -}) - test.for(RUNTIME_PROBED_PROPS)( 'the unchecked token map resolves %s to its placeholder', (prop: string) => { 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' From 3fd3f37876cbc455135f30915abfae02da83aea2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:39 +0000 Subject: [PATCH 05/15] test(js-sdk): remove redundant template utils tests Co-Authored-By: mish@e2b.dev --- .../template/utils/getCallerDirectory.test.ts | 15 --------------- .../template/utils/validateRelativePath.test.ts | 6 ------ 2 files changed, 21 deletions(-) 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', () => { From ced8870009b67dae5377249cd1a95a90f2943c84 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:51 +0000 Subject: [PATCH 06/15] test(python-sdk): remove redundant unit tests Drop duplicate/shape-only tests in client, connection config, rpc exception, logging option and idempotency suites; fold the not-SandboxNotRunning assertion into the port-closed mapping test. Co-Authored-By: mish@e2b.dev --- packages/python-sdk/tests/test_client.py | 10 ----- .../tests/test_connection_config.py | 9 ---- .../tests/test_envd_rpc_exception.py | 7 +--- .../python-sdk/tests/test_logging_option.py | 22 +--------- .../tests/test_sandbox_create_idempotency.py | 41 ------------------- 5 files changed, 2 insertions(+), 87 deletions(-) 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_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_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 = [] From 4d8bebdda8245b356a245eb9dfe764c3757bbe09 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:59 +0000 Subject: [PATCH 07/15] test(python-sdk): remove redundant sandbox tests Drop tests duplicated by stronger counterparts (secure-mode create/connect/write, command envs, pty send_input), a no-assertion watch test, a test of the test module's own wait_for_status helper, and implementation-coupled sync client lifecycle tests. Co-Authored-By: mish@e2b.dev --- .../sandbox_async/commands/test_env_vars.py | 5 - .../async/sandbox_async/files/test_watch.py | 5 - .../async/sandbox_async/files/test_write.py | 19 ---- .../sandbox_async/pty/test_send_input.py | 11 --- .../tests/async/sandbox_async/test_network.py | 10 -- .../tests/async/sandbox_async/test_secure.py | 26 ------ .../sandbox_sync/commands/test_env_vars.py | 5 - .../sync/sandbox_sync/files/test_watch.py | 7 -- .../sync/sandbox_sync/files/test_write.py | 19 ---- .../sync/sandbox_sync/pty/test_send_input.py | 9 -- .../tests/sync/sandbox_sync/test_network.py | 10 -- .../tests/sync/sandbox_sync/test_secure.py | 26 ------ .../test_sync_client_lifecycle.py | 93 ------------------- 13 files changed, 245 deletions(-) delete mode 100644 packages/python-sdk/tests/async/sandbox_async/pty/test_send_input.py delete mode 100644 packages/python-sdk/tests/async/sandbox_async/test_secure.py delete mode 100644 packages/python-sdk/tests/sync/sandbox_sync/pty/test_send_input.py delete mode 100644 packages/python-sdk/tests/sync/sandbox_sync/test_secure.py delete mode 100644 packages/python-sdk/tests/sync/sandbox_sync/test_sync_client_lifecycle.py 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/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 From 05b53c7f6be985506b1d14caa82aa5c26c46f054 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:25 +0000 Subject: [PATCH 08/15] test(python-sdk): drop empty network config dns test covered by create payload tests Co-Authored-By: mish@e2b.dev --- packages/python-sdk/tests/shared/sandbox/test_dns_servers.py | 5 ----- 1 file changed, 5 deletions(-) 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 = { From fde611a78a00835e0637632e590988055a490096 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:48:47 +0000 Subject: [PATCH 09/15] test(python-sdk): share from_dockerfile/to_dockerfile tests between sync and async Co-Authored-By: mish@e2b.dev --- .../methods/test_from_dockerfile.py | 485 ------------------ .../methods/test_to_dockerfile.py | 57 -- .../template}/methods/test_from_dockerfile.py | 0 .../template}/methods/test_to_dockerfile.py | 0 4 files changed, 542 deletions(-) delete mode 100644 packages/python-sdk/tests/async/template_async/methods/test_from_dockerfile.py delete mode 100644 packages/python-sdk/tests/async/template_async/methods/test_to_dockerfile.py rename packages/python-sdk/tests/{sync/template_sync => shared/template}/methods/test_from_dockerfile.py (100%) rename packages/python-sdk/tests/{sync/template_sync => shared/template}/methods/test_to_dockerfile.py (100%) 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 "< Date: Wed, 28 Oct 2026 21:48:47 +0000 Subject: [PATCH 10/15] test(python-sdk): share Secret.fill tests between sync and async Co-Authored-By: mish@e2b.dev --- .../tests/async/secret_async/test_secret.py | 12 +----------- .../python-sdk/tests/shared/secret/test_fill.py | 14 ++++++++++++++ .../tests/sync/secret_sync/test_secret.py | 12 +----------- 3 files changed, 16 insertions(+), 22 deletions(-) create mode 100644 packages/python-sdk/tests/shared/secret/test_fill.py 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/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/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) From 16beedefdeb0cc9ee6c0107296923be6b360660c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:50:13 +0000 Subject: [PATCH 11/15] test(python-sdk): remove redundant transport and retry unit tests Co-Authored-By: mish@e2b.dev --- .../tests/test_api_client_transport.py | 83 ----------- .../tests/test_envd_interceptors.py | 11 -- .../tests/test_rate_limit_retry_transport.py | 136 ++---------------- .../tests/test_retry_stream_buffering.py | 13 -- 4 files changed, 12 insertions(+), 231 deletions(-) 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_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_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. From c3e15d17d70ebe6d1f2965c9c23aafa209894cd0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:49:31 +0000 Subject: [PATCH 12/15] test(js-sdk): remove redundant volume tests Co-Authored-By: mish@e2b.dev --- packages/js-sdk/tests/volume/file.test.ts | 24 +----------- packages/js-sdk/tests/volume/volume.test.ts | 43 --------------------- 2 files changed, 2 insertions(+), 65 deletions(-) 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') From 8cc361309a62889aea38e31a2ffb8d6c6482b0d0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:49:31 +0000 Subject: [PATCH 13/15] test(python-sdk): remove redundant volume tests Co-Authored-By: mish@e2b.dev --- .../tests/async/volume_async/test_file.py | 32 +------ .../tests/async/volume_async/test_volume.py | 23 ----- .../tests/sync/volume_sync/test_file.py | 32 +------ .../tests/sync/volume_sync/test_volume.py | 23 ----- .../python-sdk/tests/test_volume_client.py | 88 ------------------- 5 files changed, 4 insertions(+), 194 deletions(-) diff --git a/packages/python-sdk/tests/async/volume_async/test_file.py b/packages/python-sdk/tests/async/volume_async/test_file.py index f901dcb434..fb5370f4fe 100644 --- a/packages/python-sdk/tests/async/volume_async/test_file.py +++ b/packages/python-sdk/tests/async/volume_async/test_file.py @@ -18,24 +18,14 @@ async def test_write_and_read_text_file(self, async_volume: AsyncVolume): assert read_content == content async def test_write_and_read_bytes(self, async_volume: AsyncVolume): - 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]) await async_volume.write_file(path, content_bytes) read_bytes = await async_volume.read_file(path, format="bytes") assert read_bytes == content_bytes - async def test_write_and_read_binary_data(self, async_volume: AsyncVolume): - path = "/binary.bin" - binary_data = bytes([0, 1, 2, 3, 4]) - - await async_volume.write_file(path, binary_data) - read_bytes = await async_volume.read_file(path, format="bytes") - - assert read_bytes == binary_data - async def test_write_and_read_stream(self, async_volume: AsyncVolume): path = "/test-stream.txt" content = "Test stream content" @@ -229,15 +219,6 @@ async def test_list_nested_directory(self, async_volume: AsyncVolume): assert len(entries) >= 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/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_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, ): From f3db97d6a3036da662fc56777931a1668fa2bf3f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:46:54 +0000 Subject: [PATCH 14/15] test(js-sdk): use Sandbox.pause instead of deprecated betaPause in new list test Co-Authored-By: mish@e2b.dev --- packages/js-sdk/tests/api/list.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/js-sdk/tests/api/list.test.ts b/packages/js-sdk/tests/api/list.test.ts index 644ec9ad55..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 Sandbox.betaPause(extraSbx.sandboxId) + await Sandbox.pause(extraSbx.sandboxId) try { const paginator = Sandbox.list({ From 96a4761d8022a2311ba75de92a22d5471c8d0c27 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 21:51:22 +0000 Subject: [PATCH 15/15] test: restore runtime-probed iam token validation tests --- packages/js-sdk/tests/sandbox/iam.test.ts | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/js-sdk/tests/sandbox/iam.test.ts b/packages/js-sdk/tests/sandbox/iam.test.ts index f9c00a820a..06d4f481ed 100644 --- a/packages/js-sdk/tests/sandbox/iam.test.ts +++ b/packages/js-sdk/tests/sandbox/iam.test.ts @@ -155,3 +155,30 @@ test.for(RUNTIME_PROBED_PROPS)( expect(String(tokens)).toBe('[object Object]') } ) + +test.for(RUNTIME_PROBED_PROPS)( + 'reading the unregistered token %s throws instead of resolving', + (prop: string) => { + const tokens = iamTokenPlaceholders(['aws'], { validate: true }) + + expect(() => `${tokens[prop]}`).toThrowError( + `iam token '${prop}', which is not registered` + ) + expect(() => String(tokens[prop])).toThrowError(InvalidArgumentError) + // Assigned straight through, a header value is serialized, not coerced. + expect(() => JSON.stringify({ header: tokens[prop] })).toThrowError( + InvalidArgumentError + ) + } +) + +test('the runtime-probed valueOf answers with the guarded map', () => { + // It stays callable for `String(iam.tokens)`, so it must not hand out the + // unguarded record. + const tokens = iamTokenPlaceholders(['aws'], { validate: true }) + + expect(tokens.valueOf().aws).toBe('${e2b.identity.tokens.aws}') + expect(() => `${tokens.valueOf().gcp}`).toThrowError( + /iam token 'gcp'.*Registered tokens: 'aws'/s + ) +})