Files
deepseek-harness/packages/e2b/e2b/tests/e2b.spec.ts
imccyu ec601ca13d build(vendor): rescope the vendored Cordis packages into @deepseek-ai
Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it
prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`,
`verify-translation-pairing --write` for the touched bilingual pairs,
`gen-doc-graphs`, and one typert snapshot whose ids embed character offsets.
`pnpm run rescope-vendor --check` verifies the result.

Renames nine vendored packages (cordis, cosmokit, schemastery and the six
@cordisjs plugins) and every reference that resolves them: manifest names and
dependency keys, module specifiers including declare-module merges, cordis.yml
plugin names, tsconfig paths, every Markdown fence, and `docs/` prose.
Directory names, upstream versions, and dependency ranges are unchanged, so
vendor/README.md still reads as an upstream snapshot; its manifest table gains
an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed
at each fork's origin.

The tutorial tier follows the rename end to end: its yaml fences named plugins
the Loader can no longer resolve, its `ts ignore-check` fences disagreed with
the compiled fences beside them, and its prose quoted both. The contracts that
told readers to keep upstream names — the root convention and the vendoring
cookbook's tree comment and manifest invariant — now say to rescope instead.

Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle
purity gate now names the vendored libraries a browser bundle inlines, and the
files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:13 +08:00

248 lines
9.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { Mock } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import type { Sandbox as SandboxType } from 'e2b'
import E2BSandboxService, {
e2bControlEnvs,
FileType,
SandboxNotFoundError,
quoteE2BShellArg,
} from '@deepseek-ai/dsh-e2b'
import * as E2BInvariant from '../src/invariant.ts'
import InvariantService from '@deepseek-ai/dsh-invariants'
const sdk = vi.hoisted(() => ({
create: vi.fn(),
}))
vi.mock('e2b', async (importOriginal) => {
const actual = await importOriginal<typeof import('e2b')>()
// The mock replaces only the SDK's static factory surface and is never constructed.
// oxlint-disable-next-line typescript/no-extraneous-class -- The SDK contract is a class with a static factory.
class FakeSandbox {
static create(...args: unknown[]): unknown {
return sdk.create(...args)
}
}
return { ...actual, Sandbox: FakeSandbox }
})
interface SandboxFixture {
sandbox: SandboxType
makeDir: ReturnType<typeof vi.fn>
getInfo: ReturnType<typeof vi.fn>
run: Mock<RunCommand>
kill: ReturnType<typeof vi.fn>
}
type RunCommand = (
command: string,
options?: { envs?: Record<string, string> },
) => Promise<{ exitCode: number; stdout: string; stderr: string }>
function fakeSandbox(id = 'sandbox-1'): SandboxFixture {
const makeDir = vi.fn().mockResolvedValue(true)
const getInfo = vi.fn().mockResolvedValue({ type: FileType.DIR })
const run = vi.fn<RunCommand>().mockResolvedValue({ exitCode: 0, stdout: '', stderr: '' })
const kill = vi.fn().mockResolvedValue(undefined)
const sandbox = {
sandboxId: id,
files: { makeDir, getInfo },
commands: { run },
kill,
} as unknown as SandboxType
return { sandbox, makeDir, getInfo, run, kill }
}
beforeEach(() => {
sdk.create.mockReset()
vi.unstubAllEnvs()
})
describe('E2BSandboxService', () => {
it('gives each SDK login shell a fresh non-overridable control home', () => {
const first = e2bControlEnvs({ HOME: '/hostile', NPM_TOKEN: '' })
const second = e2bControlEnvs()
expect(first.HOME).toMatch(/^\/\.dsh-e2b-control-/)
expect(first).toEqual({ HOME: first.HOME, NPM_TOKEN: '' })
expect(first.HOME).not.toBe(second.HOME)
})
it('creates one protected shared sandbox and kills it on default disposal', async () => {
const fixture = fakeSandbox()
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
const service = ctx.e2b
await expect(service.getSandbox()).resolves.toBe(fixture.sandbox)
expect(service.cwd).toBe('/home/user/workspace')
expect(service.runtimeRoot).toBe('/home/user/workspace/.dsh-e2b')
expect(sdk.create).toHaveBeenCalledWith({
apiKey: 'test-key',
timeoutMs: 300_000,
secure: true,
lifecycle: { onTimeout: 'kill' },
})
expect(fixture.makeDir).toHaveBeenNthCalledWith(1, '/home/user/workspace')
expect(fixture.makeDir).toHaveBeenNthCalledWith(2, '/home/user/workspace/.dsh-e2b')
expect(fixture.getInfo).toHaveBeenCalledWith('/home/user/workspace/.dsh-e2b')
const runOptions = fixture.run.mock.calls[0]?.[1]
expect(runOptions?.envs?.HOME).toMatch(/^\/\.dsh-e2b-control-/)
expect(fixture.run).toHaveBeenCalledWith(
"chmod 700 -- '/home/user/workspace/.dsh-e2b'",
{ envs: { HOME: runOptions?.envs?.HOME } },
)
await fiber.dispose()
expect(fixture.kill).toHaveBeenCalledOnce()
await expect(service.getSandbox()).rejects.toThrow(/disposing/)
})
it('rejects handle acquisition when disposal starts during setup', async () => {
const fixture = fakeSandbox()
const opening = Promise.withResolvers<SandboxType>()
sdk.create.mockReturnValue(opening.promise)
const ctx = new Context()
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
const acquisition = ctx.e2b.getSandbox()
const disposing = fiber.dispose()
opening.resolve(fixture.sandbox)
await expect(acquisition).rejects.toThrow(/disposing/)
await expect(disposing).resolves.toBeUndefined()
expect(fixture.kill).toHaveBeenCalledOnce()
})
it('reads the key from the environment and honors the configured cwd and lifetime', async () => {
vi.stubEnv('E2B_API_KEY', 'environment-key')
const fixture = fakeSandbox('configured-sandbox')
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const fiber = await ctx.plugin(E2BSandboxService, {
cwd: '/workspace/project',
timeoutMs: 60_000,
})
await ctx.e2b.getSandbox()
expect(sdk.create).toHaveBeenCalledWith({
apiKey: 'environment-key',
timeoutMs: 60_000,
secure: true,
lifecycle: { onTimeout: 'kill' },
})
expect(ctx.e2b.cwd).toBe('/workspace/project')
await fiber.dispose()
expect(fixture.kill).toHaveBeenCalledOnce()
})
it('accepts a missing sandbox when disposal itself requests deletion', async () => {
const fixture = fakeSandbox()
fixture.kill.mockRejectedValue(new SandboxNotFoundError('already deleted'))
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const errors: unknown[] = []
ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
await ctx.e2b.getSandbox()
await fiber.dispose()
expect(fixture.kill).toHaveBeenCalledOnce()
expect(errors).toEqual([])
})
it('does not classify other disposal failures as an already-gone sandbox', async () => {
const fixture = fakeSandbox()
const failure = new Error('disposition unknown')
fixture.kill.mockRejectedValue(failure)
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const errors: unknown[] = []
ctx.logger.error = ((error: unknown) => { errors.push(error) }) as typeof ctx.logger.error
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
await ctx.e2b.getSandbox()
await expect(fiber.dispose()).resolves.toBeUndefined()
expect(fixture.kill).toHaveBeenCalledOnce()
expect(errors).toContain(failure)
})
it('kills a newly created sandbox when remote directory setup fails', async () => {
const fixture = fakeSandbox()
fixture.makeDir.mockRejectedValueOnce(new Error('setup failed'))
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
await expect(ctx.e2b.getSandbox()).rejects.toThrow('setup failed')
expect(fixture.kill).toHaveBeenCalledOnce()
await fiber.dispose()
})
it('preserves the setup failure after its one rollback attempt fails', async () => {
const fixture = fakeSandbox()
fixture.run.mockRejectedValueOnce(new Error('chmod failed'))
fixture.kill.mockRejectedValueOnce(new Error('cleanup failed'))
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
const fiber = await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
await expect(ctx.e2b.getSandbox()).rejects.toThrow('chmod failed')
expect(fixture.kill).toHaveBeenCalledOnce()
await fiber.dispose()
expect(fixture.kill).toHaveBeenCalledOnce()
})
it.each([
['symbolic link', { type: FileType.DIR, symlinkTarget: '/tmp/redirected' }],
['regular file', { type: FileType.FILE }],
])('rejects a reserved runtime root that is a %s', async (_label, info) => {
const fixture = fakeSandbox()
fixture.getInfo.mockResolvedValueOnce(info)
sdk.create.mockResolvedValue(fixture.sandbox)
const ctx = new Context()
await ctx.plugin(E2BSandboxService, { apiKey: 'test-key' })
await expect(ctx.e2b.getSandbox()).rejects.toThrow('runtime root must be a real directory')
expect(fixture.run).not.toHaveBeenCalled()
expect(fixture.kill).toHaveBeenCalledOnce()
})
it.each([
[{ apiKey: '' }, /configure apiKey/],
[{ apiKey: 'x', cwd: 'relative' }, /absolute Linux path/],
[{ apiKey: 'x', timeoutMs: 0 }, /positive finite/],
] as const)('fails self-contained configuration before opening E2B: %j', async (config, message) => {
vi.stubEnv('E2B_API_KEY', '')
const ctx = new Context()
await expect(ctx.plugin(E2BSandboxService, config)).rejects.toThrow(message)
expect(sdk.create).not.toHaveBeenCalled()
})
it('requires a key when both config and the environment omit it', async () => {
const original = process.env.E2B_API_KEY
delete process.env.E2B_API_KEY
try {
const ctx = new Context()
await expect(ctx.plugin(E2BSandboxService, {})).rejects.toThrow(/configure apiKey/)
} finally {
if (original === undefined) delete process.env.E2B_API_KEY
else process.env.E2B_API_KEY = original
}
})
})
describe('E2B helpers and invariant companion', () => {
it('quotes opaque shell arguments without interpolation', () => {
expect(quoteE2BShellArg("a'b $HOME")).toBe("'a'\"'\"'b $HOME'")
})
it('registers the package-owned empty invariant installer', async () => {
const ctx = new Context()
await ctx.plugin(InvariantService, { enabled: true })
const fiber = await ctx.plugin(E2BInvariant).await()
await fiber.dispose()
})
})