chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:58 +08:00
commit b16403ea71
789 changed files with 115226 additions and 0 deletions
@@ -0,0 +1,133 @@
import { assert, test, describe } from 'vitest'
import { handleApiError } from '../../src/api'
import {
AuthenticationError,
RateLimitError,
SandboxError,
} from '../../src/errors'
function createMockResponse(
status: number,
error: unknown,
data?: unknown
): {
response: { status: number; ok: boolean }
error: unknown
data: unknown
} {
return {
response: { status, ok: status >= 200 && status < 300 },
error,
data,
}
}
describe('handleApiError', () => {
describe('without content', () => {
// openapi-fetch leaves `error` undefined for non-2xx responses with
// Content-Length: 0
test('catches 404 with undefined error', () => {
const res = createMockResponse(404, undefined)
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, '404')
})
test('catches 500 with undefined error', () => {
const res = createMockResponse(500, undefined)
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, '500')
})
test('returns AuthenticationError for 401 with undefined error', () => {
const res = createMockResponse(401, undefined)
const err = handleApiError(res as any)
assert.instanceOf(err, AuthenticationError)
assert.include(err?.message, 'Unauthorized')
})
})
describe('with empty error body', () => {
test('catches 404 with empty string error', () => {
const res = createMockResponse(404, '')
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, '404')
})
test('catches 400 with empty string error', () => {
const res = createMockResponse(400, '')
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, '400')
})
test('catches 500 with empty string error', () => {
const res = createMockResponse(500, '')
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, '500')
})
})
describe('with JSON error body', () => {
test('catches 404 with message', () => {
const res = createMockResponse(404, { code: 404, message: 'Not found' })
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, 'Not found')
})
test('catches 400 with message', () => {
const res = createMockResponse(400, { code: 400, message: 'Bad request' })
const err = handleApiError(res as any)
assert.instanceOf(err, SandboxError)
assert.include(err?.message, 'Bad request')
})
})
describe('special status codes', () => {
test('returns AuthenticationError for 401', () => {
const res = createMockResponse(401, { message: 'Invalid token' })
const err = handleApiError(res as any)
assert.instanceOf(err, AuthenticationError)
assert.include(err?.message, 'Unauthorized')
})
test('returns AuthenticationError for 401 with empty body', () => {
const res = createMockResponse(401, '')
const err = handleApiError(res as any)
assert.instanceOf(err, AuthenticationError)
assert.include(err?.message, 'Unauthorized')
})
test('returns RateLimitError for 429', () => {
const res = createMockResponse(429, { message: 'Too many requests' })
const err = handleApiError(res as any)
assert.instanceOf(err, RateLimitError)
assert.include(err?.message, 'Rate limit')
})
test('returns RateLimitError for 429 with empty body', () => {
const res = createMockResponse(429, '')
const err = handleApiError(res as any)
assert.instanceOf(err, RateLimitError)
assert.include(err?.message, 'Rate limit')
})
})
describe('success responses', () => {
test('returns undefined for 200 success', () => {
const res = createMockResponse(200, undefined, { id: '123' })
const err = handleApiError(res as any)
assert.isUndefined(err)
})
test('returns undefined for 201 success', () => {
const res = createMockResponse(201, undefined, { id: '123' })
const err = handleApiError(res as any)
assert.isUndefined(err)
})
})
})
+137
View File
@@ -0,0 +1,137 @@
import { afterEach, expect, test, vi } from 'vitest'
afterEach(() => {
vi.restoreAllMocks()
vi.resetModules()
vi.doUnmock('undici')
vi.doUnmock('../../src/utils')
delete process.env.E2B_API_CONNECTIONS
delete process.env.E2B_API_INFLIGHT_REQUESTS
})
test('uses undici with a bounded HTTP/2 dispatcher for API requests', async () => {
const agents: Array<{ allowH2?: boolean; connections?: number }> = []
const requests: Array<{ init?: RequestInit & { dispatcher?: unknown } }> = []
class Agent {
constructor(options: { allowH2?: boolean; connections?: number }) {
agents.push(options)
}
}
const undiciFetch = vi.fn((input, init) => {
requests.push({ init })
return Promise.resolve(new Response('ok'))
})
const loadUndici = vi.fn(() => Promise.resolve({ Agent, fetch: undiciFetch }))
const { createApiFetchForRuntime } = await import('../../src/api/http2')
const fetcher = createApiFetchForRuntime('node', {
connectionLimit: 100,
loadUndici,
})
await fetcher('https://example.com/sandboxes')
expect(loadUndici).toHaveBeenCalledOnce()
expect(agents).toEqual([{ allowH2: true, connections: 100 }])
expect(requests[0].init?.dispatcher).toBeInstanceOf(Agent)
})
test('uses a ProxyAgent dispatcher when a proxy is configured', async () => {
const proxyAgents: Array<{
uri?: string
allowH2?: boolean
connections?: number
}> = []
const agents: Array<unknown> = []
const requests: Array<{ init?: RequestInit & { dispatcher?: unknown } }> = []
class Agent {
constructor() {
agents.push(this)
}
}
class ProxyAgent {
constructor(options: {
uri?: string
allowH2?: boolean
connections?: number
}) {
proxyAgents.push(options)
}
}
const undiciFetch = vi.fn((input, init) => {
requests.push({ init })
return Promise.resolve(new Response('ok'))
})
const loadUndici = vi.fn(() =>
Promise.resolve({ Agent, ProxyAgent, fetch: undiciFetch })
)
const { createApiFetchForRuntime } = await import('../../src/api/http2')
const fetcher = createApiFetchForRuntime('node', {
connectionLimit: 100,
proxy: 'http://user:pass@127.0.0.1:8080',
loadUndici,
})
await fetcher('https://example.com/sandboxes')
expect(agents).toHaveLength(0)
expect(proxyAgents).toEqual([
{
uri: 'http://user:pass@127.0.0.1:8080',
allowH2: true,
connections: 100,
},
])
expect(requests[0].init?.dispatcher).toBeInstanceOf(ProxyAgent)
})
test('caches API fetchers per proxy', async () => {
const { createApiFetch } = await import('../../src/api/http2')
const noProxy = createApiFetch()
const proxyA = createApiFetch('http://127.0.0.1:8080')
const proxyB = createApiFetch('http://127.0.0.1:9090')
expect(createApiFetch()).toBe(noProxy)
expect(createApiFetch('http://127.0.0.1:8080')).toBe(proxyA)
expect(proxyA).not.toBe(noProxy)
expect(proxyA).not.toBe(proxyB)
})
test('getApiConnectionLimit throws on a malformed env value', async () => {
process.env.E2B_API_CONNECTIONS = 'not-a-number'
const { getApiConnectionLimit } = await import('../../src/api/http2')
expect(() => getApiConnectionLimit()).toThrow(/E2B_API_CONNECTIONS/)
})
test('getApiInflightLimit throws on a malformed env value', async () => {
process.env.E2B_API_INFLIGHT_REQUESTS = 'not-a-number'
const { getApiInflightLimit } = await import('../../src/api/http2')
expect(() => getApiInflightLimit()).toThrow(/E2B_API_INFLIGHT_REQUESTS/)
})
test('getApiInflightLimit returns 0 when explicitly disabled', async () => {
process.env.E2B_API_INFLIGHT_REQUESTS = '0'
const { getApiInflightLimit } = await import('../../src/api/http2')
expect(getApiInflightLimit()).toBe(0)
})
test('getApiInflightLimit throws on negative env value', async () => {
process.env.E2B_API_INFLIGHT_REQUESTS = '-5'
const { getApiInflightLimit } = await import('../../src/api/http2')
expect(() => getApiInflightLimit()).toThrow(/E2B_API_INFLIGHT_REQUESTS=-5/)
})
@@ -0,0 +1,75 @@
import { expect, test, vi } from 'vitest'
import { limitConcurrency } from '../../src/api/inflight'
function deferred<T>() {
let resolve!: (value: T) => void
let reject!: (reason?: unknown) => void
const promise = new Promise<T>((res, rej) => {
resolve = res
reject = rej
})
return { promise, resolve, reject }
}
test('limitConcurrency queues requests over the cap and releases on response', async () => {
const gate = deferred<Response>()
let secondStarted = false
const inner = vi.fn(async (input: RequestInfo | URL) => {
if (String(input).endsWith('/first')) return gate.promise
secondStarted = true
return new Response('second')
}) as unknown as typeof fetch
const limited = limitConcurrency(inner, 1)
const first = limited('https://example.com/first')
const second = limited('https://example.com/second')
await Promise.resolve()
await Promise.resolve()
expect(secondStarted).toBe(false)
gate.resolve(new Response('first'))
expect(await (await first).text()).toBe('first')
expect(await (await second).text()).toBe('second')
expect(secondStarted).toBe(true)
})
test('limitConcurrency releases when the underlying fetch rejects', async () => {
let calls = 0
const inner = vi.fn(async () => {
calls++
if (calls === 1) throw new Error('boom')
return new Response('ok')
}) as unknown as typeof fetch
const limited = limitConcurrency(inner, 1)
await expect(limited('https://example.com/a')).rejects.toThrow('boom')
// Slot should be free for the next request.
const res = await limited('https://example.com/b')
expect(await res.text()).toBe('ok')
})
test('limitConcurrency aborts queued requests when their signal fires', async () => {
const gate = deferred<Response>()
const inner = vi.fn(async () => gate.promise) as unknown as typeof fetch
const limited = limitConcurrency(inner, 1)
// Occupy the only slot.
const first = limited('https://example.com/first')
const controller = new AbortController()
const queued = limited('https://example.com/queued', {
signal: controller.signal,
})
// Abort the queued request before the slot frees.
controller.abort()
await expect(queued).rejects.toMatchObject({ name: 'AbortError' })
// Release the first request to make sure cleanup did not break the slot.
gate.resolve(new Response('done'))
const resp = await first
expect(await resp.text()).toBe('done')
})
+10
View File
@@ -0,0 +1,10 @@
import { expect } from 'vitest'
import { sandboxTest, isDebug } from '../setup.js'
import { Sandbox } from '../../src'
sandboxTest.skipIf(isDebug)('get sandbox info', async ({ sandbox }) => {
const info = await Sandbox.getInfo(sandbox.sandboxId)
expect(info).toBeDefined()
expect(info.sandboxId).toBe(sandbox.sandboxId)
})
+21
View File
@@ -0,0 +1,21 @@
import { expect } from 'vitest'
import { sandboxTest, isDebug } from '../setup.js'
import { Sandbox } from '../../src'
sandboxTest.skipIf(isDebug)(
'kill existing sandbox',
async ({ sandbox, sandboxTestId }) => {
await Sandbox.kill(sandbox.sandboxId)
const paginator = Sandbox.list({
query: { state: ['running'], metadata: { sandboxTestId } },
})
const sandboxes = await paginator.nextItems()
expect(sandboxes.map((s) => s.sandboxId)).not.toContain(sandbox.sandboxId)
}
)
sandboxTest.skipIf(isDebug)('kill non-existing sandbox', async () => {
await expect(Sandbox.kill('nonexistingsandbox')).resolves.toBe(false)
})
+440
View File
@@ -0,0 +1,440 @@
import { assert } from 'vitest'
import { randomUUID } from 'crypto'
import { Sandbox, SandboxInfo } from '../../src'
import { sandboxTest, isDebug } from '../setup.js'
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 = 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 extraSbx.betaPause()
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()
// Create extra paused sandbox
const extraSbx = await Sandbox.create({ metadata: { sandboxTestId } })
await extraSbx.betaPause()
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 extraSbx.betaPause()
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, '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, '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 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))
}
)
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 = 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))
}
)
@@ -0,0 +1,26 @@
import { assert } from 'vitest'
import { sandboxTest, isDebug } from '../setup.js'
import { Sandbox } from '../../src'
sandboxTest.skipIf(isDebug)('pause sandbox', async ({ sandbox }) => {
await Sandbox.pause(sandbox.sandboxId)
assert.isFalse(
await sandbox.isRunning(),
'Sandbox should not be running after pause'
)
})
sandboxTest.skipIf(isDebug)('resume sandbox', async ({ sandbox }) => {
await Sandbox.pause(sandbox.sandboxId)
assert.isFalse(
await sandbox.isRunning(),
'Sandbox should not be running after pause'
)
await Sandbox.connect(sandbox.sandboxId)
assert.isTrue(
await sandbox.isRunning(),
'Sandbox should be running after resume'
)
})
@@ -0,0 +1,91 @@
import { assert, test, describe, vi, afterEach } from 'vitest'
import { ApiClient, validateApiKey } from '../../src/api'
import { ConnectionConfig } from '../../src/connectionConfig'
import { AuthenticationError } from '../../src/errors'
describe('validateApiKey', () => {
const validKey = 'e2b_' + '0123456789abcdef'.repeat(2) + '01234567'
test('accepts a well-formed key', () => {
assert.doesNotThrow(() => validateApiKey(validKey))
})
test('rejects a key without the e2b_ prefix', () => {
assert.throws(
() => validateApiKey('sk_' + '0'.repeat(40)),
AuthenticationError,
/Invalid API key format/
)
})
test('accepts a key with a non-default body length', () => {
assert.doesNotThrow(() => validateApiKey('e2b_' + '0'.repeat(20)))
})
test('rejects an empty body after the prefix', () => {
assert.throws(
() => validateApiKey('e2b_'),
AuthenticationError,
/Invalid API key format/
)
})
test('rejects a key with non-hex characters in the body', () => {
assert.throws(
() => validateApiKey('e2b_' + 'z'.repeat(40)),
AuthenticationError,
/Invalid API key format/
)
})
test('error message includes an example token', () => {
try {
validateApiKey('nope')
assert.fail('expected validateApiKey to throw')
} catch (err) {
assert.instanceOf(err, AuthenticationError)
assert.match(
(err as Error).message,
/e2b_0{40}/,
'expected example token in error message'
)
}
})
})
describe('ApiClient API key validation', () => {
test('throws on a malformed key by default', () => {
const config = new ConnectionConfig({ apiKey: 'not-a-valid-key' })
assert.throws(() => new ApiClient(config), AuthenticationError)
})
test('skips validation when validateApiKey is false', () => {
const config = new ConnectionConfig({
apiKey: 'not-a-valid-key',
validateApiKey: false,
})
assert.doesNotThrow(() => new ApiClient(config))
})
})
describe('ApiClient API key requirement', () => {
afterEach(() => {
vi.unstubAllEnvs()
})
test('throws when no API key is supplied', () => {
vi.stubEnv('E2B_API_KEY', '')
const config = new ConnectionConfig({})
assert.throws(
() => new ApiClient(config),
AuthenticationError,
/API key is required/
)
})
test('does not require an API key when requireApiKey is false', () => {
vi.stubEnv('E2B_API_KEY', '')
const config = new ConnectionConfig({})
assert.doesNotThrow(() => new ApiClient(config, { requireApiKey: false }))
})
})