import { GeminiToolHandler } from '../tools/handlers.js';
import { InMemorySessionStorage } from '../session/storage.js';
import { executeCommand } from '../utils/command.js';
// Mock the command execution
jest.mock('../utils/command.js', () => ({
executeCommand: jest.fn(),
}));
const mockedExecuteCommand = executeCommand as jest.MockedFunction<
typeof executeCommand
>;
describe('Codex Resume Functionality', () => {
let handler: GeminiToolHandler;
let sessionStorage: InMemorySessionStorage;
beforeEach(() => {
sessionStorage = new InMemorySessionStorage();
handler = new GeminiToolHandler(sessionStorage);
mockedExecuteCommand.mockClear();
});
test('should use exec for new session without conversation ID', async () => {
const sessionId = sessionStorage.createSession();
mockedExecuteCommand.mockResolvedValue({
stdout: 'Test response',
stderr: 'Conversation ID: abc-123-def',
});
await handler.execute({
prompt: 'First message',
sessionId,
});
expect(mockedExecuteCommand).toHaveBeenCalledWith('codex', [
'exec',
'--model',
'gpt-5-codex',
'--skip-git-repo-check',
'First message',
]);
});
test('should extract and store conversation ID', async () => {
const sessionId = sessionStorage.createSession();
mockedExecuteCommand.mockResolvedValue({
stdout: 'Test response',
stderr: 'Conversation ID: abc-123-def',
});
await handler.execute({
prompt: 'First message',
sessionId,
});
expect(sessionStorage.getGeminiConversationId(sessionId)).toBe(
'abc-123-def'
);
});
test('should use resume for subsequent messages in session', async () => {
const sessionId = sessionStorage.createSession();
sessionStorage.setGeminiConversationId(
sessionId,
'existing-conversation-id'
);
mockedExecuteCommand.mockResolvedValue({
stdout: 'Resumed response',
stderr: '',
});
await handler.execute({
prompt: 'Continue conversation',
sessionId,
});
expect(mockedExecuteCommand).toHaveBeenCalledWith('codex', [
'resume',
'existing-conversation-id',
'--model',
'gpt-5-codex',
'--skip-git-repo-check',
'Continue conversation',
]);
});
test('should reset conversation ID when session is reset', async () => {
const sessionId = sessionStorage.createSession();
sessionStorage.setGeminiConversationId(sessionId, 'old-conversation-id');
mockedExecuteCommand.mockResolvedValue({
stdout: 'Test response',
stderr: 'Conversation ID: new-conversation-id',
});
await handler.execute({
prompt: 'Reset and start new',
sessionId,
resetSession: true,
});
// Should use exec (not resume) and get new conversation ID
expect(mockedExecuteCommand).toHaveBeenCalledWith('codex', [
'exec',
'--model',
'gpt-5-codex',
'--skip-git-repo-check',
'Reset and start new',
]);
expect(sessionStorage.getGeminiConversationId(sessionId)).toBe(
'new-conversation-id'
);
});
test('should fall back to manual context if no conversation ID', async () => {
const sessionId = sessionStorage.createSession();
// Add some history
sessionStorage.addTurn(sessionId, {
prompt: 'Previous question',
response: 'Previous answer',
timestamp: new Date(),
});
mockedExecuteCommand.mockResolvedValue({
stdout: 'Context-aware response',
stderr: '',
});
await handler.execute({
prompt: 'Follow up question',
sessionId,
});
// Should build enhanced prompt since no conversation ID
const call = mockedExecuteCommand.mock.calls[0];
const sentPrompt = call?.[1]?.[4]; // After exec, --model, gpt-5-codex, --skip-git-repo-check, prompt
expect(sentPrompt).toContain('Context:');
expect(sentPrompt).toContain('Task: Follow up question');
});
});