@debugmcp/mcp-debugger
Version:
Run-time step-through debugging for LLM agents.
220 lines (187 loc) • 5.45 kB
text/typescript
/**
* Mock implementation of ProxyManager for testing
*/
import { EventEmitter } from 'events';
import { DebugProtocol } from '@vscode/debugprotocol';
import { IProxyManager, ProxyConfig, ProxyManagerEvents } from '../../src/proxy/proxy-manager.js';
/**
* Mock ProxyManager for unit testing
*/
export class MockProxyManager extends EventEmitter implements IProxyManager {
private _isRunning = false;
private _currentThreadId: number | null = null;
private _config: ProxyConfig | null = null;
private _dapRequestHandler: ((command: string, args?: any) => Promise<any>) | null = null;
// Track calls for testing
public startCalls: ProxyConfig[] = [];
public stopCalls: number = 0;
public dapRequestCalls: Array<{ command: string; args?: any }> = [];
// Control behavior for testing
public shouldFailStart = false;
public startDelay = 0;
public shouldFailDapRequests = false;
public dapRequestDelay = 0;
constructor() {
super();
}
async start(config: ProxyConfig): Promise<void> {
this.startCalls.push(config);
if (this.shouldFailStart) {
throw new Error('Mock start failure');
}
if (this.startDelay > 0) {
await new Promise(resolve => setTimeout(resolve, this.startDelay));
}
this._config = config;
this._isRunning = true;
// Simulate initialization
process.nextTick(() => {
if (config.dryRunSpawn) {
this.emit('dry-run-complete', 'python', config.scriptPath);
} else {
this.emit('adapter-configured');
this.emit('initialized');
// If stopOnEntry is true, simulate stop
if (config.stopOnEntry) {
this._currentThreadId = 1;
this.emit('stopped', 1, 'entry');
}
}
});
}
async stop(): Promise<void> {
this.stopCalls++;
this._isRunning = false;
this._currentThreadId = null;
this._config = null;
process.nextTick(() => {
this.emit('exit', 0, undefined);
});
}
async sendDapRequest<T extends DebugProtocol.Response>(
command: string,
args?: any
): Promise<T> {
this.dapRequestCalls.push({ command, args });
if (!this._isRunning) {
throw new Error('Proxy not running');
}
if (this.shouldFailDapRequests) {
throw new Error(`Mock DAP request failure: ${command}`);
}
if (this.dapRequestDelay > 0) {
await new Promise(resolve => setTimeout(resolve, this.dapRequestDelay));
}
// Use custom handler if provided
if (this._dapRequestHandler) {
const result = await this._dapRequestHandler(command, args);
return result as T;
}
// Default mock responses
switch (command) {
case 'setBreakpoints':
return {
success: true,
body: {
breakpoints: args?.breakpoints?.map((bp: any) => ({
verified: true,
line: bp.line
})) || []
}
} as T;
case 'stackTrace':
return {
success: true,
body: {
stackFrames: [{
id: 1,
name: 'main',
source: { path: 'test.py' },
line: 10,
column: 0
}]
}
} as T;
case 'scopes':
return {
success: true,
body: {
scopes: [{
name: 'Locals',
variablesReference: 100,
expensive: false
}]
}
} as T;
case 'variables':
return {
success: true,
body: {
variables: [{
name: 'test_var',
value: '42',
type: 'int',
variablesReference: 0
}]
}
} as T;
case 'next':
case 'stepIn':
case 'stepOut':
// Simulate step completion
process.nextTick(() => {
this.emit('stopped', this._currentThreadId || 1, 'step');
});
return { success: true } as T;
case 'continue':
process.nextTick(() => {
this.emit('continued');
});
return { success: true } as T;
default:
return { success: true } as T;
}
}
isRunning(): boolean {
return this._isRunning;
}
getCurrentThreadId(): number | null {
return this._currentThreadId;
}
// Test helpers
setDapRequestHandler(handler: (command: string, args?: any) => Promise<any>): void {
this._dapRequestHandler = handler;
}
simulateEvent<K extends keyof ProxyManagerEvents>(
event: K,
...args: Parameters<ProxyManagerEvents[K]>
): void {
this.emit(event, ...args);
}
simulateStopped(threadId: number, reason: string): void {
this._currentThreadId = threadId;
this.emit('stopped', threadId, reason);
}
simulateError(error: Error): void {
this.emit('error', error);
}
simulateExit(code: number, signal?: string): void {
this._isRunning = false;
this._currentThreadId = null;
this.emit('exit', code, signal);
}
reset(): void {
this.startCalls = [];
this.stopCalls = 0;
this.dapRequestCalls = [];
this.shouldFailStart = false;
this.startDelay = 0;
this.shouldFailDapRequests = false;
this.dapRequestDelay = 0;
this._isRunning = false;
this._currentThreadId = null;
this._config = null;
this._dapRequestHandler = null;
this.removeAllListeners();
}
}