agent-ecosystem/test/features/recent-projects/main/infrastructure/CodexAppServerClient.test.ts

211 lines
6.9 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { CodexAppServerClient } from '@features/recent-projects/main/infrastructure/codex/CodexAppServerClient';
import type { JsonRpcSession, JsonRpcStdioClient } from '@features/recent-projects/main/infrastructure/codex/JsonRpcStdioClient';
function createSession(
request: JsonRpcSession['request'],
notify: JsonRpcSession['notify'] = vi.fn().mockResolvedValue(undefined)
): JsonRpcSession {
return {
request,
notify,
};
}
describe('CodexAppServerClient', () => {
it('loads live and archived threads in a single app-server session', async () => {
const session = createSession(
vi.fn().mockImplementation((method: string, params?: { archived?: boolean }) => {
if (method === 'initialize') {
return Promise.resolve({});
}
if (method === 'thread/list' && params?.archived === false) {
return Promise.resolve({
data: [{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' }],
});
}
if (method === 'thread/list' && params?.archived === true) {
return Promise.resolve({
data: [{ id: 'archived-1', cwd: '/Users/test/archive-project', source: 'vscode' }],
});
}
return Promise.reject(new Error(`Unexpected method: ${method}`));
})
);
const withSession = vi.fn().mockImplementation((_options, handler) => handler(session));
const client = new CodexAppServerClient({ withSession } as unknown as JsonRpcStdioClient);
const result = await client.listRecentThreads('/usr/local/bin/codex', {
limit: 40,
liveRequestTimeoutMs: 4500,
archivedRequestTimeoutMs: 2500,
totalTimeoutMs: 4500,
});
expect(withSession).toHaveBeenCalledTimes(1);
expect(withSession).toHaveBeenCalledWith(
expect.objectContaining({
binaryPath: '/usr/local/bin/codex',
requestTimeoutMs: 4500,
totalTimeoutMs: 12000,
}),
expect.any(Function)
);
expect(session.notify).toHaveBeenCalledWith('initialized');
expect(result).toEqual({
live: {
threads: [{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' }],
},
archived: {
threads: [{ id: 'archived-1', cwd: '/Users/test/archive-project', source: 'vscode' }],
},
});
});
it('keeps live results when archived thread loading fails', async () => {
const session = createSession(
vi.fn().mockImplementation((method: string, params?: { archived?: boolean }) => {
if (method === 'initialize') {
return Promise.resolve({});
}
if (method === 'thread/list' && params?.archived === false) {
return Promise.resolve({
data: [{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' }],
});
}
if (method === 'thread/list' && params?.archived === true) {
return Promise.reject(new Error('JSON-RPC request timed out: thread/list'));
}
return Promise.reject(new Error(`Unexpected method: ${method}`));
})
);
const withSession = vi.fn().mockImplementation((_options, handler) => handler(session));
const client = new CodexAppServerClient({ withSession } as unknown as JsonRpcStdioClient);
const result = await client.listRecentThreads('/usr/local/bin/codex', {
limit: 40,
liveRequestTimeoutMs: 4500,
archivedRequestTimeoutMs: 2500,
totalTimeoutMs: 4500,
});
expect(result.live.threads).toEqual([
{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' },
]);
expect(result.archived).toEqual({
threads: [],
error: 'JSON-RPC request timed out: thread/list',
});
});
it('raises the session timeout budget above the longest request timeout', async () => {
const session = createSession(
vi.fn().mockImplementation((method: string, params?: { archived?: boolean }) => {
if (method === 'initialize') {
return Promise.resolve({});
}
if (method === 'thread/list') {
return Promise.resolve({ data: [] });
}
return Promise.reject(new Error(`Unexpected method: ${method}`));
})
);
const withSession = vi.fn().mockImplementation((_options, handler) => handler(session));
const client = new CodexAppServerClient({ withSession } as unknown as JsonRpcStdioClient);
await client.listRecentThreads('/usr/local/bin/codex', {
limit: 40,
liveRequestTimeoutMs: 4500,
archivedRequestTimeoutMs: 2500,
totalTimeoutMs: 4500,
});
expect(withSession).toHaveBeenCalledWith(
expect.objectContaining({
totalTimeoutMs: 12000,
}),
expect.any(Function)
);
});
it('can load only live threads in a dedicated fallback session', async () => {
const session = createSession(
vi.fn().mockImplementation((method: string, params?: { archived?: boolean }) => {
if (method === 'initialize') {
return Promise.resolve({});
}
if (method === 'thread/list' && params?.archived === false) {
return Promise.resolve({
data: [{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' }],
});
}
return Promise.reject(new Error(`Unexpected method: ${method}`));
})
);
const withSession = vi.fn().mockImplementation((_options, handler) => handler(session));
const client = new CodexAppServerClient({ withSession } as unknown as JsonRpcStdioClient);
const result = await client.listRecentLiveThreads('/usr/local/bin/codex', {
limit: 40,
requestTimeoutMs: 4500,
totalTimeoutMs: 6000,
});
expect(withSession).toHaveBeenCalledWith(
expect.objectContaining({
binaryPath: '/usr/local/bin/codex',
requestTimeoutMs: 4500,
totalTimeoutMs: 12000,
label: 'codex app-server thread/list live',
}),
expect.any(Function)
);
expect(result).toEqual({
threads: [{ id: 'live-1', cwd: '/Users/test/live-project', source: 'cli' }],
});
});
it('uses the longer initialize timeout for app-server startup', async () => {
const request = vi.fn().mockImplementation((method: string, _params?: unknown, timeoutMs?: number) => {
if (method === 'initialize') {
expect(timeoutMs).toBe(6000);
return Promise.resolve({});
}
if (method === 'thread/list') {
return Promise.resolve({ data: [] });
}
return Promise.reject(new Error(`Unexpected method: ${method}`));
});
const session = createSession(request);
const withSession = vi.fn().mockImplementation((_options, handler) => handler(session));
const client = new CodexAppServerClient({ withSession } as unknown as JsonRpcStdioClient);
await client.listRecentThreads('/usr/local/bin/codex', {
limit: 40,
liveRequestTimeoutMs: 4500,
archivedRequestTimeoutMs: 2500,
totalTimeoutMs: 4500,
});
expect(request).toHaveBeenCalled();
});
});