Skip to main content
Glama
ooples

MCP Console Automation Server

TestRecorder.test.ts11.1 kB
/** * Unit tests for TestRecorder */ import { TestRecorder } from '../testing/TestRecorder.js'; import * as fs from 'fs'; import * as path from 'path'; describe('TestRecorder', () => { const testOutputDir = 'data/recordings/test'; let recorder: TestRecorder; beforeEach(() => { recorder = new TestRecorder(testOutputDir); // Clean up test directory if (fs.existsSync(testOutputDir)) { const files = fs.readdirSync(testOutputDir); files.forEach((file) => { fs.unlinkSync(path.join(testOutputDir, file)); }); } }); afterEach(() => { // Clean up if (recorder.isCurrentlyRecording()) { try { recorder.cancelRecording(); } catch (e) { // Ignore } } }); describe('startRecording', () => { it('should start a new recording', () => { recorder.startRecording({ name: 'test-recording', author: 'Test Author', description: 'Test description', tags: ['test', 'unit'], }); expect(recorder.isCurrentlyRecording()).toBe(true); expect(recorder.getRecording()).not.toBeNull(); expect(recorder.getRecording()?.name).toBe('test-recording'); }); it('should throw error if recording already in progress', () => { recorder.startRecording({ name: 'test1' }); expect(() => { recorder.startRecording({ name: 'test2' }); }).toThrow('Recording already in progress'); }); it('should capture environment metadata', () => { recorder.startRecording({ name: 'test-env' }); const recording = recorder.getRecording(); expect(recording?.metadata.environment).toBeDefined(); expect(recording?.metadata.environment?.platform).toBe(process.platform); }); }); describe('stopRecording', () => { it('should stop recording and save to file', () => { recorder.startRecording({ name: 'test-stop' }); recorder.recordSendInput('echo hello', 'session1'); const result = recorder.stopRecording(); expect(result.name).toBe('test-stop'); expect(result.steps.length).toBe(1); expect(recorder.isCurrentlyRecording()).toBe(false); // Check file was created const filepath = path.join(testOutputDir, 'test-stop.json'); expect(fs.existsSync(filepath)).toBe(true); }); it('should throw error if no recording in progress', () => { expect(() => { recorder.stopRecording(); }).toThrow('No recording in progress'); }); it('should include duration in saved recording', () => { recorder.startRecording({ name: 'test-duration' }); const result = recorder.stopRecording(); expect(result.duration).toBeGreaterThanOrEqual(0); }); }); describe('recordCreateSession', () => { it('should record a create_session step', () => { recorder.startRecording({ name: 'test-create-session' }); const sessionData = { command: 'bash', cwd: '/tmp' }; recorder.recordCreateSession( 'session123', sessionData, 'Session created' ); const recording = recorder.getRecording(); expect(recording?.steps.length).toBe(1); expect(recording?.steps[0].type).toBe('create_session'); expect(recording?.steps[0].sessionId).toBe('session123'); expect(recording?.steps[0].data).toEqual(sessionData); expect(recording?.steps[0].output).toBe('Session created'); }); it('should set currentSessionId', () => { recorder.startRecording({ name: 'test-session-id' }); recorder.recordCreateSession('session456', {}); // Verify by recording another step without sessionId recorder.recordSendInput('test', undefined, 'output'); const steps = recorder.getRecording()?.steps; expect(steps?.[1].sessionId).toBe('session456'); }); }); describe('recordSendInput', () => { it('should record a send_input step', () => { recorder.startRecording({ name: 'test-input' }); recorder.recordSendInput('echo test', 'session1', 'test output'); const steps = recorder.getRecording()?.steps; expect(steps?.length).toBe(1); expect(steps?.[0].type).toBe('send_input'); expect(steps?.[0].data.input).toBe('echo test'); expect(steps?.[0].sessionId).toBe('session1'); }); it('should use current session ID if not specified', () => { recorder.startRecording({ name: 'test-current-session' }); recorder.recordCreateSession('session999', {}); recorder.recordSendInput('test input'); const steps = recorder.getRecording()?.steps; expect(steps?.[1].sessionId).toBe('session999'); }); }); describe('recordSendKey', () => { it('should record a send_key step', () => { recorder.startRecording({ name: 'test-key' }); recorder.recordSendKey('enter', 'session1'); const steps = recorder.getRecording()?.steps; expect(steps?.length).toBe(1); expect(steps?.[0].type).toBe('send_key'); expect(steps?.[0].data.key).toBe('enter'); }); }); describe('recordWaitForOutput', () => { it('should record a wait_for_output step', () => { recorder.startRecording({ name: 'test-wait' }); recorder.recordWaitForOutput('prompt>', 5000, 'session1', 'matched'); const steps = recorder.getRecording()?.steps; expect(steps?.length).toBe(1); expect(steps?.[0].type).toBe('wait_for_output'); expect(steps?.[0].data.pattern).toBe('prompt>'); expect(steps?.[0].data.timeout).toBe(5000); }); }); describe('getStats', () => { it('should return recording statistics', () => { recorder.startRecording({ name: 'test-stats' }); recorder.recordSendInput('test1'); recorder.recordSendInput('test2'); recorder.recordSendInput('test3'); const stats = recorder.getStats(); expect(stats).not.toBeNull(); expect(stats?.steps).toBe(3); expect(stats?.duration).toBeGreaterThanOrEqual(0); }); it('should return null if no recording', () => { const stats = recorder.getStats(); expect(stats).toBeNull(); }); }); describe('cancelRecording', () => { it('should cancel recording without saving', () => { recorder.startRecording({ name: 'test-cancel' }); recorder.recordSendInput('test'); recorder.cancelRecording(); expect(recorder.isCurrentlyRecording()).toBe(false); expect(recorder.getRecording()).toBeNull(); // Verify file was not created const files = fs.existsSync(testOutputDir) ? fs.readdirSync(testOutputDir) : []; expect(files.length).toBe(0); }); it('should throw error if no recording in progress', () => { expect(() => { recorder.cancelRecording(); }).toThrow('No recording in progress'); }); }); describe('Static methods', () => { describe('listRecordings', () => { it('should list all recordings in directory', () => { // Create some test recordings recorder.startRecording({ name: 'test1' }); recorder.stopRecording(); recorder.startRecording({ name: 'test2' }); recorder.stopRecording(); const recordings = TestRecorder.listRecordings(testOutputDir); expect(recordings.length).toBe(2); expect(recordings).toContain('test1'); expect(recordings).toContain('test2'); }); it('should return empty array if directory does not exist', () => { const recordings = TestRecorder.listRecordings('nonexistent/dir'); expect(recordings).toEqual([]); }); }); describe('loadRecording', () => { it('should load a recording from file', () => { recorder.startRecording({ name: 'test-load' }); recorder.recordSendInput('test data'); recorder.stopRecording(); const loaded = TestRecorder.loadRecording('test-load', testOutputDir); expect(loaded.name).toBe('test-load'); expect(loaded.steps.length).toBe(1); }); it('should throw error if recording not found', () => { expect(() => { TestRecorder.loadRecording('nonexistent', testOutputDir); }).toThrow('Recording not found'); }); it('should handle .json extension', () => { recorder.startRecording({ name: 'test-ext' }); recorder.stopRecording(); const loaded = TestRecorder.loadRecording( 'test-ext.json', testOutputDir ); expect(loaded.name).toBe('test-ext'); }); }); describe('deleteRecording', () => { it('should delete a recording file', () => { recorder.startRecording({ name: 'test-delete' }); recorder.stopRecording(); const filepath = path.join(testOutputDir, 'test-delete.json'); expect(fs.existsSync(filepath)).toBe(true); TestRecorder.deleteRecording('test-delete', testOutputDir); expect(fs.existsSync(filepath)).toBe(false); }); it('should not throw if file does not exist', () => { expect(() => { TestRecorder.deleteRecording('nonexistent', testOutputDir); }).not.toThrow(); }); }); }); describe('Timestamp tracking', () => { it('should record relative timestamps for steps', async () => { recorder.startRecording({ name: 'test-timing' }); recorder.recordSendInput('step1'); await new Promise((resolve) => setTimeout(resolve, 10)); recorder.recordSendInput('step2'); await new Promise((resolve) => setTimeout(resolve, 10)); recorder.recordSendInput('step3'); const steps = recorder.getRecording()?.steps; expect(steps?.[0].timestamp).toBeLessThanOrEqual(5); // First step should be close to 0 expect(steps?.[1].timestamp).toBeGreaterThan(0); expect(steps?.[2].timestamp).toBeGreaterThan(steps?.[1].timestamp); }); }); describe('Edge cases', () => { it('should handle special characters in filenames', () => { recorder.startRecording({ name: 'test@#$%^&*()' }); recorder.stopRecording(); const recordings = TestRecorder.listRecordings(testOutputDir); expect(recordings.length).toBe(1); }); it('should handle empty steps array', () => { recorder.startRecording({ name: 'test-empty' }); const result = recorder.stopRecording(); expect(result.steps.length).toBe(0); expect(result.duration).toBeGreaterThanOrEqual(0); }); it('should preserve metadata through save/load cycle', () => { const metadata = { author: 'John Doe', description: 'Test recording', tags: ['integration', 'ssh'], environment: { test: 'value' }, }; recorder.startRecording({ name: 'test-metadata', ...metadata, }); recorder.stopRecording(); const loaded = TestRecorder.loadRecording('test-metadata', testOutputDir); expect(loaded.metadata.author).toBe(metadata.author); expect(loaded.metadata.description).toBe(metadata.description); expect(loaded.metadata.tags).toEqual(metadata.tags); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ooples/mcp-console-automation'

If you have feedback or need assistance with the MCP directory API, please join our Discord server