agent-ecosystem/test/main/features/codex-runtime-installer/CodexRuntimeInstallerService.test.ts
2026-05-13 22:30:25 +03:00

238 lines
8 KiB
TypeScript

import { createHash } from 'crypto';
import { mkdir, mkdtemp, rm, writeFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import { gzipSync } from 'zlib';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
const execCliMock = vi.hoisted(() => vi.fn());
vi.mock('@main/utils/childProcess', () => ({
execCli: execCliMock,
}));
import {
extractCodexRuntimePackageFilesFromTarball,
getCodexRuntimePlatformCandidates,
resolveAppManagedCodexRuntimeBinaryPath,
resolveVerifiedAppManagedCodexRuntimeBinaryPath,
verifyCodexRuntimePackageIntegrity,
} from '@features/codex-runtime-installer/main';
import { setAppDataBasePath } from '@main/utils/pathDecoder';
let tempRoot: string | null = null;
function writeOctal(header: Buffer, offset: number, length: number, value: number): void {
const encoded = value
.toString(8)
.padStart(length - 1, '0')
.slice(-(length - 1));
header.write(`${encoded}\0`, offset, length, 'ascii');
}
function createTarEntry(name: string, data: Buffer): Buffer {
const header = Buffer.alloc(512);
header.write(name, 0, Math.min(Buffer.byteLength(name), 100), 'utf8');
writeOctal(header, 100, 8, 0o755);
writeOctal(header, 108, 8, 0);
writeOctal(header, 116, 8, 0);
writeOctal(header, 124, 12, data.length);
writeOctal(header, 136, 12, 0);
header.fill(' ', 148, 156);
header.write('0', 156, 1, 'ascii');
header.write('ustar\0', 257, 6, 'ascii');
header.write('00', 263, 2, 'ascii');
const checksum = header.reduce((sum, byte) => sum + byte, 0);
const checksumText = checksum.toString(8).padStart(6, '0');
header.write(`${checksumText}\0 `, 148, 8, 'ascii');
const padding = Buffer.alloc((512 - (data.length % 512)) % 512);
return Buffer.concat([header, data, padding]);
}
function createTarball(entries: { name: string; data: string }[]): Buffer {
return gzipSync(
Buffer.concat([
...entries.map((entry) => createTarEntry(entry.name, Buffer.from(entry.data))),
Buffer.alloc(1024),
])
);
}
describe('CodexRuntimeInstallerService resolver', () => {
beforeEach(async () => {
tempRoot = await mkdtemp(path.join(os.tmpdir(), 'codex-runtime-resolver-'));
setAppDataBasePath(tempRoot);
execCliMock.mockReset();
execCliMock.mockResolvedValue({ stdout: 'codex-cli 1.0.0\n', stderr: '' });
});
afterEach(async () => {
setAppDataBasePath(null);
if (tempRoot) {
await rm(tempRoot, { recursive: true, force: true });
tempRoot = null;
}
});
it('returns the current app-managed Codex binary path only when manifest and binary exist', async () => {
const binaryPath = path.join(
tempRoot!,
'data',
'runtimes',
'codex',
'versions',
'1.0.0-darwin-arm64',
'aarch64-apple-darwin',
'codex',
'codex'
);
const manifestPath = path.join(tempRoot!, 'data', 'runtimes', 'codex', 'current.json');
await mkdir(path.dirname(binaryPath), { recursive: true });
await mkdir(path.dirname(manifestPath), { recursive: true });
await writeFile(binaryPath, 'binary', { mode: 0o755 });
await writeFile(
manifestPath,
`${JSON.stringify({
schemaVersion: 1,
rootVersion: '1.0.0',
platformVersion: '1.0.0-darwin-arm64',
platformTarget: 'aarch64-apple-darwin',
binaryPath,
integrity: 'sha512-test',
installedAt: '2026-05-13T00:00:00.000Z',
})}\n`,
'utf8'
);
expect(resolveAppManagedCodexRuntimeBinaryPath()).toBe(binaryPath);
});
it('ignores a manifest whose binary path is missing', async () => {
const manifestPath = path.join(tempRoot!, 'data', 'runtimes', 'codex', 'current.json');
await mkdir(path.dirname(manifestPath), { recursive: true });
await writeFile(
manifestPath,
`${JSON.stringify({
schemaVersion: 1,
rootVersion: '1.0.0',
platformVersion: '1.0.0-darwin-arm64',
platformTarget: 'aarch64-apple-darwin',
binaryPath: path.join(tempRoot!, 'missing-codex'),
integrity: 'sha512-test',
installedAt: '2026-05-13T00:00:00.000Z',
})}\n`,
'utf8'
);
expect(resolveAppManagedCodexRuntimeBinaryPath()).toBeNull();
});
it('returns the verified app-managed binary path only when --version succeeds', async () => {
const binaryPath = path.join(
tempRoot!,
'data',
'runtimes',
'codex',
'versions',
'1.0.0-darwin-arm64',
'aarch64-apple-darwin',
'codex',
'codex'
);
const manifestPath = path.join(tempRoot!, 'data', 'runtimes', 'codex', 'current.json');
await mkdir(path.dirname(binaryPath), { recursive: true });
await mkdir(path.dirname(manifestPath), { recursive: true });
await writeFile(binaryPath, 'binary', { mode: 0o755 });
await writeFile(
manifestPath,
`${JSON.stringify({
schemaVersion: 1,
rootVersion: '1.0.0',
platformVersion: '1.0.0-darwin-arm64',
platformTarget: 'aarch64-apple-darwin',
binaryPath,
integrity: 'sha512-test',
installedAt: '2026-05-13T00:00:00.000Z',
})}\n`,
'utf8'
);
await expect(resolveVerifiedAppManagedCodexRuntimeBinaryPath()).resolves.toBe(binaryPath);
expect(execCliMock).toHaveBeenCalledWith(binaryPath, ['--version'], {
timeout: 10_000,
windowsHide: true,
});
execCliMock.mockRejectedValueOnce(new Error('broken binary'));
await expect(resolveVerifiedAppManagedCodexRuntimeBinaryPath()).resolves.toBeNull();
});
});
describe('CodexRuntimeInstallerService package safety helpers', () => {
it('selects expected platform packages', () => {
expect(
getCodexRuntimePlatformCandidates('darwin', 'arm64').map(
(item) => item.optionalDependencyName
)
).toEqual(['@openai/codex-darwin-arm64']);
expect(
getCodexRuntimePlatformCandidates('darwin', 'x64').map((item) => item.vendorTarget)
).toEqual(['x86_64-apple-darwin']);
expect(
getCodexRuntimePlatformCandidates('linux', 'x64').map((item) => item.vendorTarget)
).toEqual(['x86_64-unknown-linux-musl']);
expect(
getCodexRuntimePlatformCandidates('linux', 'arm64').map((item) => item.vendorTarget)
).toEqual(['aarch64-unknown-linux-musl']);
expect(
getCodexRuntimePlatformCandidates('win32', 'x64').map((item) => item.vendorTarget)
).toEqual(['x86_64-pc-windows-msvc']);
expect(
getCodexRuntimePlatformCandidates('win32', 'arm64').map((item) => item.vendorTarget)
).toEqual(['aarch64-pc-windows-msvc']);
});
it('fails npm integrity mismatches', () => {
const payload = Buffer.from('actual package');
const wrongHash = createHash('sha512').update('different package').digest('base64');
expect(() => verifyCodexRuntimePackageIntegrity(payload, `sha512-${wrongHash}`)).toThrow(
'integrity check failed'
);
});
it('extracts the full selected Codex vendor payload from the package tarball', () => {
const tarball = createTarball([
{ name: 'package/vendor/other-target/codex/codex', data: 'wrong' },
{ name: 'package/vendor/aarch64-apple-darwin/codex/codex', data: 'codex-binary' },
{ name: 'package/vendor/aarch64-apple-darwin/path/rg', data: 'rg-binary' },
]);
const files = extractCodexRuntimePackageFilesFromTarball(
tarball,
'aarch64-apple-darwin',
'codex'
);
expect(files.map((file) => file.relativePath).sort((a, b) => a.localeCompare(b))).toEqual([
'codex/codex',
'path/rg',
]);
expect(files.find((file) => file.relativePath === 'codex/codex')?.data.toString()).toBe(
'codex-binary'
);
});
it('rejects tar path traversal before extraction', () => {
const tarball = createTarball([
{ name: '../codex', data: 'unsafe' },
{ name: 'package/vendor/aarch64-apple-darwin/codex/codex', data: 'right' },
]);
expect(() =>
extractCodexRuntimePackageFilesFromTarball(tarball, 'aarch64-apple-darwin', 'codex')
).toThrow('Unsafe Codex package tar entry');
});
});