import { afterEach, describe, expect, test, vi } from 'vitest'; const loadVolumeSpec = async () => { vi.resetModules(); process.env.SPOON_WORKER_TOKEN = 'test-worker-token'; process.env.GITHUB_APP_ID = '123'; process.env.GITHUB_APP_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\\ntest\\n-----END PRIVATE KEY-----'; return await import('../../src/runtime/docker'); }; describe('Docker runtime', () => { afterEach(() => { delete process.env.SPOON_AGENT_CONTAINER_RUNTIME; delete process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS; vi.resetModules(); }); test('adds SELinux relabel option for Podman workspace mounts by default', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman'; const { jobWorkspaceVolumeSpec } = await loadVolumeSpec(); expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe( '/tmp/spoon-job:/workspace:Z', ); }); test('does not add Podman volume options for Docker by default', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'docker'; const { jobWorkspaceVolumeSpec } = await loadVolumeSpec(); expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe( '/tmp/spoon-job:/workspace', ); }); test('allows explicit workspace mount options', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman'; process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS = 'z'; const { jobWorkspaceVolumeSpec } = await loadVolumeSpec(); expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe( '/tmp/spoon-job:/workspace:z', ); }); test('boxUsernsArgs maps the host user onto the box uid under podman', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman'; const { boxUsernsArgs } = await loadVolumeSpec(); expect(boxUsernsArgs()).toEqual(['--userns=keep-id:uid=1000,gid=1000']); }); test('boxUsernsArgs adds nothing under docker', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'docker'; const { boxUsernsArgs } = await loadVolumeSpec(); expect(boxUsernsArgs()).toEqual([]); }); test('SPOON_AGENT_BOX_USERNS overrides: value passes through, empty disables', async () => { process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman'; process.env.SPOON_AGENT_BOX_USERNS = 'host'; let { boxUsernsArgs } = await loadVolumeSpec(); expect(boxUsernsArgs()).toEqual(['--userns=host']); process.env.SPOON_AGENT_BOX_USERNS = ''; ({ boxUsernsArgs } = await loadVolumeSpec()); expect(boxUsernsArgs()).toEqual([]); delete process.env.SPOON_AGENT_BOX_USERNS; }); test('execArgv places -u after the workdir and before the container name', async () => { const { execArgv } = await loadVolumeSpec(); expect( execArgv({ containerName: 'spoon-box-gabriel', command: ['ls', '-la'], environment: { HOME: '/home/gabriel' }, containerCwd: '/home/gabriel', user: 'gabriel', }), ).toEqual([ 'exec', '-e', 'HOME=/home/gabriel', '-w', '/home/gabriel', '-u', 'gabriel', 'spoon-box-gabriel', 'ls', '-la', ]); }); test('execArgv omits -u when no user is given', async () => { const { execArgv } = await loadVolumeSpec(); const argv = execArgv({ containerName: 'c', command: ['true'], environment: {}, containerCwd: '/', }); expect(argv).not.toContain('-u'); }); test('treats a spawn failure (no exitCode) as a non-zero exit, not empty success', async () => { const { normalizeRunResult } = await loadVolumeSpec(); // This is what execa returns with `reject: false` when the runtime binary is // missing (e.g. no `docker` CLI in the worker image): exitCode is undefined. const result = normalizeRunResult( { exitCode: undefined, shortMessage: 'spawn docker ENOENT' }, undefined, (value) => value, ); expect(result.exitCode).toBe(1); expect(result.output).toContain('spawn docker ENOENT'); }); test('passes through a normal command result unchanged', async () => { const { normalizeRunResult } = await loadVolumeSpec(); const result = normalizeRunResult( { exitCode: 0, shortMessage: undefined }, 'hello', (value) => value, ); expect(result).toEqual({ exitCode: 0, output: 'hello' }); }); });