import { createHash } from 'node:crypto' import { EventEmitter } from 'node:events' import { PassThrough } from 'node:stream' import { describe, expect, it, vi } from 'vitest' import { parseControlResponse, validateControlRequest } from '../codex-link-control' import controlSchema from '../codex-link-control.schema.json' import { CODEX_LINK_CONTROL_SCHEMA_HASH } from '../codex-link-control.types' import type { CodexLinkControlRequest } from '../codex-link-control.types' import { canonicalJson } from '../codex-link-json' import { createCodexLinkClient } from './codex-link-client' const request: CodexLinkControlRequest = { contract_version: 'codex-link/1', kind: 'settings_get', request_id: 'req-1', operation_id: 'op-1' } const response = { ...request, kind: 'settings_result', settings: { settings_revision: 1, enabled: true, compression_enabled: true, threshold_tokens: 250000, source_binding_handle: null, compact_route_id: null, legacy_intent_imported: false, ownership_manifest_revision: 0, activation_state: 'pending_runtime_acceptance' }, available_sources: [] } function harness(reply: string = JSON.stringify(response), env: Record = {}) { let stdin = '' const child = Object.assign(new EventEmitter(), { stdout: new PassThrough(), stderr: new PassThrough(), stdin: new PassThrough(), kill: vi.fn() }) child.stdin.on('data', chunk => { stdin += chunk.toString() }) child.stdin.on('finish', () => queueMicrotask(() => { const bytes = Buffer.from(reply) // UTF-8 chunks may end inside a multi-byte character. for (const byte of bytes) { child.stdout.write(Buffer.from([byte])) } child.stderr.write('diagnostic that must not become a response') child.emit('close', 0) }) ) const spawn = vi.fn(() => child) const control = createCodexLinkClient({ spawn, existsSync: () => true, platform: 'win32', env: { LOCALAPPDATA: 'C:\\Users\\Example User\\AppData\\Local', SystemRoot: 'C:\\Windows', ...env } }) return { control, spawn, child, stdin: () => stdin } } describe('Codex connection control boundary', () => { it('pins the generated control-only schema', () => { expect('sha256:' + createHash('sha256').update(canonicalJson(controlSchema)).digest('hex')).toBe(CODEX_LINK_CONTROL_SCHEMA_HASH) expect(controlSchema.$defs).not.toHaveProperty('runtime_attach') expect(controlSchema.$defs).not.toHaveProperty('checkpoint_commit') }) it('uses only fixed command arguments and sends the exact request on stdin with EOF', async () => { const h = harness() expect(await h.control(request)).toEqual(response) expect(h.stdin()).toBe(JSON.stringify(request) + '\n') const [file, args, options] = h.spawn.mock.calls[0] as unknown as [string, string[], any] expect(file).toBe('C:\\Windows\\System32\\cmd.exe') expect(args).toEqual([ '/d', '/s', '/c', '""C:\\Users\\Example User\\AppData\\Local\\TurkServis\\AITURK\\program\\bin\\aiturk.cmd" codex-link control --stdin"' ]) expect(options).toMatchObject({ shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }) }) it('rejects runtime and arbitrary path/credential commands before spawning', async () => { const h = harness() await expect(h.control({ ...request, kind: 'runtime_attach' })).rejects.toThrow() await expect(h.control({ ...request, path: 'C:\\private' })).rejects.toThrow() expect(h.spawn).not.toHaveBeenCalled() }) it('rejects shell expansion in the entrypoint path', async () => { const h = harness(undefined, { LOCALAPPDATA: 'C:\\Users\\%PATH%\\Local' }) await expect(h.control(request)).rejects.toThrow('invalid_entrypoint') expect(h.spawn).not.toHaveBeenCalled() }) it('binds the response to both request and operation identity', () => { expect(() => parseControlResponse(JSON.stringify({ ...response, operation_id: 'other-chat-op' }), request) ).toThrow() expect(() => parseControlResponse(JSON.stringify({ ...response, request_id: 'other-request' }), request)).toThrow() }) it('rejects duplicate keys, floats, trailing JSON and invalid UTF8', () => { expect(() => parseControlResponse( JSON.stringify(response).replace('"settings_revision":1', '"settings_revision":1,"settings_revision":2'), request ) ).toThrow('duplicate_json_key') expect(() => parseControlResponse(JSON.stringify(response).replace('250000', '250000.0'), request)).toThrow( 'floats_or_non_json_type' ) expect(() => parseControlResponse(JSON.stringify(response) + '{}', request)).toThrow() expect(() => parseControlResponse(new Uint8Array([0xc3, 0x28]), request)).toThrow() }) it('rejects oversized requests and unsafe integer settings', () => { expect(() => validateControlRequest({ ...request, kind: 'settings_update', expected_settings_revision: 1, patch: { threshold_tokens: Number.MAX_SAFE_INTEGER + 1 } }) ).toThrow() expect(() => validateControlRequest({ ...request, operation_id: 'x'.repeat(70000) })).toThrow() }) it('does not retry a write after a broken reply', async () => { const h = harness('broken') await expect(h.control({ ...request, kind: 'catalog_sync' })).rejects.toThrow('outcome_unknown') expect(h.spawn).toHaveBeenCalledTimes(1) }) it('preserves a structured CLI rejection returned with exit code one', async () => { const rejection = { ...request, kind: 'control_error', settings_revision: 2, error: { code: 'stale_settings_cas', message: 'Ayarlar değişti.', recovery_action: 'retry_control_read' } } const h = harness() h.child.stdin.removeAllListeners('finish') h.child.stdin.on('finish', () => queueMicrotask(() => { h.child.stdout.write(JSON.stringify(rejection)) h.child.emit('close', 1) })) expect(await h.control(request)).toEqual(rejection) expect(h.spawn).toHaveBeenCalledTimes(1) }) it('preserves exact Unicode when response bytes arrive separately', async () => { const h = harness( JSON.stringify({ ...response, available_sources: [{ source_binding_handle: 'source-a', label: 'Türkçe é/é 🧪', authorized: true }] }) ) const result = await h.control(request) expect(result.kind === 'settings_result' && result.available_sources[0].label).toBe('Türkçe é/é 🧪') }) it('bounds stdout and terminates only the short proxy', async () => { const h = harness('x'.repeat(1024 * 1024 + 1)) // Avoid a million small chunks: replace the synthetic finish listener. h.child.stdin.removeAllListeners('finish') h.child.stdin.on('finish', () => queueMicrotask(() => h.child.stdout.emit('data', Buffer.alloc(1024 * 1024 + 1)))) await expect(h.control(request)).rejects.toThrow('outcome_unknown') expect(h.child.kill).toHaveBeenCalledTimes(1) }) })