import { assert, expect, test } from 'vitest' import { InvalidArgumentError, Sandbox } from '../../src' import { isDebug, template, wait } from '../setup.js' test.skipIf(isDebug)( 'filesystem-only auto-pause cannot be combined with auto-resume', async () => { // A filesystem-only auto-pause snapshot can only be resumed explicitly, so // keepMemory:false with autoResume is rejected client-side. await expect( Sandbox.create(template, { timeoutMs: 3_000, lifecycle: { onTimeout: { action: 'pause', keepMemory: false }, autoResume: true, }, }) ).rejects.toThrowError(InvalidArgumentError) } ) test.skipIf(isDebug)( 'keepMemory is not allowed when onTimeout action is kill', async () => { // The discriminated union forbids keepMemory on `action: 'kill'` at compile // time (asserted by @ts-expect-error). The runtime guard below additionally // rejects it for untyped (JS) callers that bypass the type. await expect( Sandbox.create(template, { timeoutMs: 3_000, lifecycle: { // @ts-expect-error keepMemory is not allowed with action: 'kill' onTimeout: { action: 'kill', keepMemory: false }, }, }) ).rejects.toThrowError(InvalidArgumentError) } ) test.skipIf(isDebug)( 'auto-pause without auto-resume requires connect to wake', async () => { const sandbox = await Sandbox.create(template, { timeoutMs: 3_000, lifecycle: { onTimeout: 'pause', autoResume: false, }, }) try { await wait(5_000) assert.equal((await sandbox.getInfo()).state, 'paused') assert.isFalse(await sandbox.isRunning()) await sandbox.connect() assert.equal((await sandbox.getInfo()).state, 'running') assert.isTrue(await sandbox.isRunning()) } finally { await sandbox.kill().catch(() => {}) } }, 60_000 ) test.skipIf(isDebug)( 'filesystem-only auto-pause reboots on connect', async () => { // keepMemory:false makes the timeout auto-pause filesystem-only, so resuming // cold-boots the sandbox from disk. const sandbox = await Sandbox.create(template, { timeoutMs: 3_000, lifecycle: { onTimeout: { action: 'pause', keepMemory: false } }, }) try { const marker = 'auto-pause-fs-only' await sandbox.files.write('/home/user/auto-pause-marker.txt', marker) const bootBefore = ( await sandbox.files.read('/proc/sys/kernel/random/boot_id') ).trim() await wait(5_000) assert.equal((await sandbox.getInfo()).state, 'paused') // A filesystem-only snapshot cannot auto-resume on traffic; connect // resumes it by cold-booting. await sandbox.connect() const persisted = ( await sandbox.files.read('/home/user/auto-pause-marker.txt') ).trim() assert.equal(persisted, marker) const bootAfter = ( await sandbox.files.read('/proc/sys/kernel/random/boot_id') ).trim() assert.notEqual(bootAfter, bootBefore) } finally { await sandbox.kill().catch(() => {}) } }, 60_000 ) test.skipIf(isDebug)( 'auto-resume wakes paused sandbox on http request', async () => { const sandbox = await Sandbox.create(template, { timeoutMs: 3_000, lifecycle: { onTimeout: 'pause', autoResume: true, }, }) try { await sandbox.commands.run('python3 -m http.server 8000', { background: true, }) await wait(5_000) const url = `https://${sandbox.getHost(8000)}` const res = await fetch(url, { signal: AbortSignal.timeout(15_000) }) assert.equal(res.status, 200) assert.equal((await sandbox.getInfo()).state, 'running') assert.isTrue(await sandbox.isRunning()) } finally { await sandbox.kill().catch(() => {}) } }, 60_000 )