@debugmcp/mcp-debugger
Version:
Run-time step-through debugging for LLM agents.
166 lines (136 loc) • 6.05 kB
text/typescript
/**
* SessionManager integration tests
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { SessionManager, SessionManagerConfig } from '../../../../src/session/session-manager.js';
import { DebugLanguage, SessionState } from '../../../../src/session/models.js';
import { createMockDependencies } from './session-manager-test-utils.js';
describe('SessionManager - Integration Tests', () => {
let sessionManager: SessionManager;
let dependencies: ReturnType<typeof createMockDependencies>;
let config: SessionManagerConfig;
beforeEach(() => {
vi.useFakeTimers({ shouldAdvanceTime: true });
dependencies = createMockDependencies();
config = {
logDirBase: '/tmp/test-sessions',
defaultDapLaunchArgs: {
stopOnEntry: true,
justMyCode: true
}
};
sessionManager = new SessionManager(config, dependencies);
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
dependencies.mockProxyManager.reset();
});
describe('Event Handling', () => {
it('should forward ProxyManager events correctly', async () => {
const session = await sessionManager.createSession({
language: DebugLanguage.MOCK,
pythonPath: 'python'
});
await sessionManager.startDebugging(session.id, 'test.py');
await vi.runAllTimersAsync();
// Test various events
dependencies.mockProxyManager.simulateEvent('stopped', 1, 'breakpoint');
expect(sessionManager.getSession(session.id)?.state).toBe(SessionState.PAUSED);
dependencies.mockProxyManager.simulateEvent('continued');
expect(sessionManager.getSession(session.id)?.state).toBe(SessionState.RUNNING);
dependencies.mockProxyManager.simulateEvent('terminated');
expect(sessionManager.getSession(session.id)?.state).toBe(SessionState.STOPPED);
});
it('should handle auto-continue for stopOnEntry=false', async () => {
const session = await sessionManager.createSession({
language: DebugLanguage.MOCK,
pythonPath: 'python'
});
// Configure logger spy
const loggerSpy = vi.spyOn(dependencies.logger, 'info');
await sessionManager.startDebugging(session.id, 'test.py', [], { stopOnEntry: false });
await vi.runAllTimersAsync();
// Simulate entry stop
dependencies.mockProxyManager.simulateEvent('stopped', 1, 'entry');
// Should log auto-continue message
expect(loggerSpy).toHaveBeenCalledWith(
expect.stringContaining('Auto-continuing (stopOnEntry=false)')
);
});
});
describe('Logger Integration', () => {
it('should log all major operations', async () => {
const session = await sessionManager.createSession({
language: DebugLanguage.MOCK,
pythonPath: 'python'
});
expect(dependencies.logger.info).toHaveBeenCalledWith(
expect.stringContaining('Created new session')
);
await sessionManager.startDebugging(session.id, 'test.py');
// Check that some variation of "start debugging" was logged
// The logger.info call has two arguments - the message and the dapLaunchArgs (which may be undefined)
expect(dependencies.logger.info).toHaveBeenCalledWith(
expect.stringMatching(/[Aa]ttempting to start debugging/),
undefined // No dapLaunchArgs were provided, so it logs undefined
);
await sessionManager.closeSession(session.id);
expect(dependencies.logger.info).toHaveBeenCalledWith(
expect.stringContaining('Closing debug session')
);
});
it('should log errors appropriately', async () => {
const session = await sessionManager.createSession({
language: DebugLanguage.MOCK,
pythonPath: 'python'
});
dependencies.mockProxyManager.shouldFailStart = true;
await sessionManager.startDebugging(session.id, 'test.py');
// The error logger is called with the full error message as one argument
expect(dependencies.logger.error).toHaveBeenCalled();
const errorCall = (dependencies.logger.error as any).mock.calls.find((call: any[]) =>
call[0].includes('Error during startDebugging')
);
expect(errorCall).toBeDefined();
});
});
describe('Integration with SessionStore', () => {
it('should persist sessions correctly', async () => {
const session1 = await sessionManager.createSession({
language: DebugLanguage.MOCK,
name: 'Session 1',
pythonPath: 'python'
});
const session2 = await sessionManager.createSession({
language: DebugLanguage.MOCK,
name: 'Session 2',
pythonPath: 'python'
});
// Check they're in the store
const allSessions = sessionManager.getAllSessions();
expect(allSessions).toHaveLength(2);
expect(allSessions).toContainEqual(
expect.objectContaining({ id: session1.id, name: 'Session 1' })
);
expect(allSessions).toContainEqual(
expect.objectContaining({ id: session2.id, name: 'Session 2' })
);
});
it('should update session state in store', async () => {
const session = await sessionManager.createSession({
language: DebugLanguage.MOCK,
pythonPath: 'python'
});
const initialUpdatedAt = session.updatedAt;
// Use fake timers to advance time
vi.advanceTimersByTime(100);
await sessionManager.startDebugging(session.id, 'test.py');
await vi.runAllTimersAsync(); // Allow events to process
const updatedSession = sessionManager.getSession(session.id);
// State transitions to PAUSED because stopOnEntry=true and the mock immediately emits a stopped event
expect(updatedSession?.state).toBe(SessionState.PAUSED);
expect(updatedSession?.updatedAt?.getTime()).toBeGreaterThan(initialUpdatedAt?.getTime() || 0);
});
});
});