@probelabs/probe
Version:
Node.js wrapper for the probe code search tool
371 lines (300 loc) • 12.3 kB
JavaScript
// Tests for ACP Server
import { jest } from '@jest/globals';
import { ACPServer } from './server.js';
import { ACPConnection } from './connection.js';
import { ACP_PROTOCOL_VERSION, RequestMethod, SessionMode, ErrorCode } from './types.js';
// Mock manually handled below
describe('ACPServer', () => {
let server;
let mockConnection;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Create mock connection
mockConnection = {
start: jest.fn(),
sendResponse: jest.fn(),
sendError: jest.fn(),
sendNotification: jest.fn(),
on: jest.fn()
};
// Mock the ACPConnection constructor
ACPConnection.mockImplementation(() => mockConnection);
server = new ACPServer({
debug: true,
provider: 'anthropic',
path: '/test/path'
});
});
describe('initialization', () => {
test('should create server with default options', () => {
const defaultServer = new ACPServer();
expect(defaultServer.options.debug).toBe(false);
expect(defaultServer.sessions.size).toBe(0);
expect(defaultServer.initialized).toBe(false);
});
test('should create server with custom options', () => {
expect(server.options.debug).toBe(true);
expect(server.options.provider).toBe('anthropic');
expect(server.options.path).toBe('/test/path');
});
test('should have correct capabilities', () => {
const capabilities = server.getCapabilities();
expect(capabilities.tools).toHaveLength(3);
expect(capabilities.tools[0].name).toBe('search');
expect(capabilities.tools[1].name).toBe('query');
expect(capabilities.tools[2].name).toBe('extract');
expect(capabilities.sessionManagement).toBe(true);
expect(capabilities.streaming).toBe(true);
});
});
describe('server startup', () => {
test('should start server and setup connection', async () => {
await server.start();
expect(ACPConnection).toHaveBeenCalledWith(process.stdin, process.stderr);
expect(mockConnection.on).toHaveBeenCalledWith('request', expect.any(Function));
expect(mockConnection.on).toHaveBeenCalledWith('notification', expect.any(Function));
expect(mockConnection.on).toHaveBeenCalledWith('error', expect.any(Function));
expect(mockConnection.on).toHaveBeenCalledWith('disconnect', expect.any(Function));
expect(mockConnection.start).toHaveBeenCalled();
});
});
describe('initialize request', () => {
test('should handle valid initialize request', async () => {
const params = { protocolVersion: ACP_PROTOCOL_VERSION };
const result = await server.handleInitialize(params);
expect(result).toEqual({
protocolVersion: ACP_PROTOCOL_VERSION,
serverInfo: {
name: 'probe-agent-acp',
version: '1.0.0',
description: 'Probe AI agent with code search capabilities'
},
capabilities: server.capabilities
});
expect(server.initialized).toBe(true);
});
test('should reject invalid protocol version', async () => {
const params = { protocolVersion: '2.0' };
await expect(server.handleInitialize(params)).rejects.toThrow('Unsupported protocol version: 2.0');
});
test('should require protocolVersion parameter', async () => {
await expect(server.handleInitialize({})).rejects.toThrow('Invalid params: protocolVersion required');
await expect(server.handleInitialize(null)).rejects.toThrow('Invalid params: protocolVersion required');
});
});
describe('session management', () => {
test('should create new session', async () => {
const result = await server.handleNewSession({});
expect(result.sessionId).toBeDefined();
expect(result.mode).toBe(SessionMode.NORMAL);
expect(result.createdAt).toBeDefined();
expect(server.sessions.has(result.sessionId)).toBe(true);
});
test('should create session with custom ID and mode', async () => {
const customId = 'test-session-123';
const params = {
sessionId: customId,
mode: SessionMode.PLANNING
};
const result = await server.handleNewSession(params);
expect(result.sessionId).toBe(customId);
expect(result.mode).toBe(SessionMode.PLANNING);
expect(server.sessions.has(customId)).toBe(true);
});
test('should load existing session', async () => {
// Create a session first
const createResult = await server.handleNewSession({});
const sessionId = createResult.sessionId;
// Load the session
const loadResult = await server.handleLoadSession({ sessionId });
expect(loadResult.id).toBe(sessionId);
expect(loadResult.mode).toBe(SessionMode.NORMAL);
expect(loadResult.historyLength).toBe(0);
expect(loadResult.toolCallsCount).toBe(0);
});
test('should fail to load non-existent session', async () => {
const params = { sessionId: 'non-existent' };
await expect(server.handleLoadSession(params)).rejects.toThrow('Session not found: non-existent');
});
test('should set session mode', async () => {
// Create a session first
const createResult = await server.handleNewSession({});
const sessionId = createResult.sessionId;
// Set mode
const result = await server.handleSetSessionMode({
sessionId,
mode: SessionMode.PLANNING
});
expect(result.success).toBe(true);
expect(mockConnection.sendNotification).toHaveBeenCalledWith(
'sessionUpdated',
{ sessionId, mode: SessionMode.PLANNING }
);
const session = server.sessions.get(sessionId);
expect(session.mode).toBe(SessionMode.PLANNING);
});
});
describe('request handling', () => {
test('should handle requests and send responses', async () => {
const message = {
method: RequestMethod.INITIALIZE,
params: { protocolVersion: ACP_PROTOCOL_VERSION },
id: 1
};
await server.handleRequest(message);
expect(mockConnection.sendResponse).toHaveBeenCalledWith(1, expect.objectContaining({
protocolVersion: ACP_PROTOCOL_VERSION
}));
});
test('should handle unknown methods', async () => {
const message = {
method: 'unknownMethod',
params: {},
id: 2
};
await server.handleRequest(message);
expect(mockConnection.sendError).toHaveBeenCalledWith(
2,
ErrorCode.METHOD_NOT_FOUND,
'Unknown method: unknownMethod'
);
});
test('should handle request errors', async () => {
const message = {
method: RequestMethod.LOAD_SESSION,
params: { sessionId: 'invalid' },
id: 3
};
await server.handleRequest(message);
expect(mockConnection.sendError).toHaveBeenCalledWith(
3,
ErrorCode.INTERNAL_ERROR,
'Session not found: invalid'
);
});
});
describe('prompt handling', () => {
let mockAgent;
beforeEach(async () => {
// Import and mock ProbeAgent
const { ProbeAgent } = await import('../ProbeAgent.js');
mockAgent = {
answer: jest.fn().mockResolvedValue('Test response'),
cancel: jest.fn()
};
ProbeAgent.mockImplementation(() => mockAgent);
// Create a session
await server.handleNewSession({ sessionId: 'test-session' });
});
test('should handle prompt request successfully', async () => {
const params = {
sessionId: 'test-session',
message: 'Test question'
};
const result = await server.handlePrompt(params);
expect(mockAgent.answer).toHaveBeenCalledWith('Test question');
expect(result).toEqual({
content: [{ type: 'text', text: 'Test response' }],
sessionId: 'test-session',
timestamp: expect.any(String)
});
// Check that history was updated
const session = server.sessions.get('test-session');
expect(session.history).toHaveLength(2);
expect(session.history[0].role).toBe('user');
expect(session.history[1].role).toBe('assistant');
});
test('should handle prompt errors gracefully', async () => {
mockAgent.answer.mockRejectedValue(new Error('AI Error'));
const params = {
sessionId: 'test-session',
message: 'Test question'
};
const result = await server.handlePrompt(params);
expect(result).toEqual({
content: [{ type: 'text', text: 'Error: AI Error' }],
sessionId: 'test-session',
timestamp: expect.any(String),
error: true
});
});
test('should require sessionId and message', async () => {
await expect(server.handlePrompt({})).rejects.toThrow('Invalid params: sessionId and message required');
await expect(server.handlePrompt({ sessionId: 'test' })).rejects.toThrow('Invalid params: sessionId and message required');
await expect(server.handlePrompt({ message: 'test' })).rejects.toThrow('Invalid params: sessionId and message required');
});
test('should require valid session', async () => {
const params = {
sessionId: 'invalid-session',
message: 'Test question'
};
await expect(server.handlePrompt(params)).rejects.toThrow('Session not found: invalid-session');
});
});
describe('cancel handling', () => {
let mockAgent;
beforeEach(async () => {
const { ProbeAgent } = await import('../ProbeAgent.js');
mockAgent = {
answer: jest.fn().mockResolvedValue('Test response'),
cancel: jest.fn()
};
ProbeAgent.mockImplementation(() => mockAgent);
// Create a session and trigger agent creation
await server.handleNewSession({ sessionId: 'test-session' });
await server.handlePrompt({
sessionId: 'test-session',
message: 'Test question'
});
});
test('should cancel session operations', async () => {
const result = await server.handleCancel({ sessionId: 'test-session' });
expect(mockAgent.cancel).toHaveBeenCalled();
expect(result.success).toBe(true);
});
test('should handle cancel for session without agent', async () => {
await server.handleNewSession({ sessionId: 'new-session' });
const result = await server.handleCancel({ sessionId: 'new-session' });
expect(result.success).toBe(true);
});
test('should require sessionId for cancel', async () => {
await expect(server.handleCancel({})).rejects.toThrow('Invalid params: sessionId required');
});
});
describe('disconnect handling', () => {
let mockAgent;
beforeEach(async () => {
const { ProbeAgent } = await import('../ProbeAgent.js');
mockAgent = {
answer: jest.fn().mockResolvedValue('Test response'),
cancel: jest.fn()
};
ProbeAgent.mockImplementation(() => mockAgent);
// Create sessions with agents
await server.handleNewSession({ sessionId: 'session1' });
await server.handleNewSession({ sessionId: 'session2' });
await server.handlePrompt({ sessionId: 'session1', message: 'test' });
await server.handlePrompt({ sessionId: 'session2', message: 'test' });
});
test('should clean up sessions and cancel agents on disconnect', () => {
expect(server.sessions.size).toBe(2);
server.handleDisconnect();
expect(mockAgent.cancel).toHaveBeenCalledTimes(2);
expect(server.sessions.size).toBe(0);
});
});
describe('stats', () => {
test('should return server statistics', async () => {
await server.handleNewSession({});
await server.handleNewSession({});
server.initialized = true;
const stats = server.getStats();
expect(stats).toEqual({
sessions: 2,
initialized: true,
capabilities: server.capabilities
});
});
});
});