Skip to main content
Glama

Cut-Copy-Paste Clipboard Server

integration.test.ts•16.4 kB
/** * Integration Tests - End-to-End Workflows * * These tests verify complete workflows across all components: * - Copy → Paste → Undo * - Cut → Paste → Verify source removed * - Multi-file paste operations */ import fs from 'fs'; import path from 'path'; import os from 'os'; import { DatabaseManager } from '../lib/database.js'; import { SessionManager } from '../lib/session-manager.js'; import { FileHandler } from '../lib/file-handler.js'; import { ClipboardManager } from '../lib/clipboard-manager.js'; import { OperationLogger } from '../lib/operation-logger.js'; import { ClipboardTools } from '../tools/clipboard-tools.js'; describe('Integration Tests - End-to-End Workflows', () => { let dbManager: DatabaseManager; let sessionManager: SessionManager; let fileHandler: FileHandler; let clipboardManager: ClipboardManager; let operationLogger: OperationLogger; let clipboardTools: ClipboardTools; let sessionId: string; let testDir: string; beforeEach(() => { // Use in-memory database for testing dbManager = new DatabaseManager(':memory:'); sessionManager = new SessionManager(dbManager); fileHandler = new FileHandler(); clipboardManager = new ClipboardManager(dbManager); operationLogger = new OperationLogger(dbManager); clipboardTools = new ClipboardTools( fileHandler, clipboardManager, operationLogger, sessionManager ); // Create session sessionId = sessionManager.createSession(); // Create temporary test directory testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-clipboard-test-')); }); afterEach(() => { // Clean up test directory if (fs.existsSync(testDir)) { fs.rmSync(testDir, { recursive: true, force: true }); } // Close database dbManager.close(); }); describe('Copy → Paste → Undo Workflow', () => { test('should complete full copy-paste-undo workflow', async () => { // Setup: Create source file const sourceFile = path.join(testDir, 'source.txt'); const sourceContent = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n'; fs.writeFileSync(sourceFile, sourceContent); // Setup: Create destination file const destFile = path.join(testDir, 'destination.txt'); const destContent = 'Dest Line 1\nDest Line 2\nDest Line 3\n'; fs.writeFileSync(destFile, destContent); // Step 1: Copy lines 2-3 from source const copyResult = await clipboardTools.copyLines(sessionId, sourceFile, 2, 3); expect(copyResult.success).toBe(true); expect(copyResult.lines).toEqual(['Line 2', 'Line 3']); // Verify source file unchanged const sourceAfterCopy = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterCopy).toBe(sourceContent); // Step 2: Show clipboard to verify const clipboardResult = await clipboardTools.showClipboard(sessionId); expect(clipboardResult.hasContent).toBe(true); expect(clipboardResult.content).toBe('Line 2\nLine 3'); expect(clipboardResult.sourceFile).toBe(sourceFile); expect(clipboardResult.operationType).toBe('copy'); // Step 3: Paste to destination at line 2 const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: destFile, target_line: 2 }, ]); expect(pasteResult.success).toBe(true); expect(pasteResult.pastedTo).toHaveLength(1); expect(pasteResult.pastedTo[0].file).toBe(destFile); // Verify destination file has pasted content const destAfterPaste = fs.readFileSync(destFile, 'utf-8'); const expectedAfterPaste = 'Dest Line 1\nLine 2\nLine 3\nDest Line 2\nDest Line 3\n'; expect(destAfterPaste).toBe(expectedAfterPaste); // Step 4: Undo the paste const undoResult = await clipboardTools.undoLastPaste(sessionId); expect(undoResult.success).toBe(true); expect(undoResult.restoredFiles).toHaveLength(1); expect(undoResult.restoredFiles[0].file).toBe(destFile); // Verify destination file restored to original const destAfterUndo = fs.readFileSync(destFile, 'utf-8'); expect(destAfterUndo).toBe(destContent); // Step 5: Verify operation history const historyResult = await clipboardTools.getOperationHistory(sessionId, 10); // Operations should be logged: copy, paste, undo expect(historyResult.operations.length).toBeGreaterThanOrEqual(3); // Operations are returned in reverse chronological order (most recent first) // Verify all operation types are present const opTypes = historyResult.operations.map((op) => op.operationType); expect(opTypes).toContain('copy'); expect(opTypes).toContain('paste'); expect(opTypes).toContain('undo'); // Verify the operations occurred in the correct sequence // Note: Due to timestamp precision, we just verify all three types are present // The ORDER is validated by the undo operation working correctly above }); test('should handle copy-paste to multiple files', async () => { // Setup: Create source file const sourceFile = path.join(testDir, 'source.txt'); const sourceContent = 'Line 1\nLine 2\nLine 3\n'; fs.writeFileSync(sourceFile, sourceContent); // Setup: Create multiple destination files const dest1 = path.join(testDir, 'dest1.txt'); const dest2 = path.join(testDir, 'dest2.txt'); const dest3 = path.join(testDir, 'dest3.txt'); fs.writeFileSync(dest1, 'Dest1 Line 1\nDest1 Line 2\n'); fs.writeFileSync(dest2, 'Dest2 Line 1\nDest2 Line 2\n'); fs.writeFileSync(dest3, 'Dest3 Line 1\nDest3 Line 2\n'); // Step 1: Copy lines from source const copyResult = await clipboardTools.copyLines(sessionId, sourceFile, 1, 2); expect(copyResult.success).toBe(true); // Step 2: Paste to multiple destinations const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: dest1, target_line: 1 }, { file_path: dest2, target_line: 2 }, { file_path: dest3, target_line: 1 }, ]); expect(pasteResult.success).toBe(true); expect(pasteResult.pastedTo).toHaveLength(3); // Verify all destination files have pasted content const dest1Content = fs.readFileSync(dest1, 'utf-8'); expect(dest1Content).toContain('Line 1'); expect(dest1Content).toContain('Line 2'); const dest2Content = fs.readFileSync(dest2, 'utf-8'); expect(dest2Content).toContain('Line 1'); expect(dest2Content).toContain('Line 2'); const dest3Content = fs.readFileSync(dest3, 'utf-8'); expect(dest3Content).toContain('Line 1'); expect(dest3Content).toContain('Line 2'); // Step 3: Undo should restore all files const undoResult = await clipboardTools.undoLastPaste(sessionId); expect(undoResult.success).toBe(true); expect(undoResult.restoredFiles).toHaveLength(3); // Verify all files restored expect(fs.readFileSync(dest1, 'utf-8')).toBe('Dest1 Line 1\nDest1 Line 2\n'); expect(fs.readFileSync(dest2, 'utf-8')).toBe('Dest2 Line 1\nDest2 Line 2\n'); expect(fs.readFileSync(dest3, 'utf-8')).toBe('Dest3 Line 1\nDest3 Line 2\n'); }); }); describe('Cut → Paste → Verify Workflow', () => { test('should complete full cut-paste workflow', async () => { // Setup: Create source file const sourceFile = path.join(testDir, 'source.txt'); const sourceContent = 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6\n'; fs.writeFileSync(sourceFile, sourceContent); // Setup: Create destination file const destFile = path.join(testDir, 'destination.txt'); const destContent = 'Dest Line 1\nDest Line 2\n'; fs.writeFileSync(destFile, destContent); // Step 1: Cut lines 3-5 from source const cutResult = await clipboardTools.cutLines(sessionId, sourceFile, 3, 5); expect(cutResult.success).toBe(true); expect(cutResult.lines).toEqual(['Line 3', 'Line 4', 'Line 5']); // Verify source file has lines removed const sourceAfterCut = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterCut).toBe('Line 1\nLine 2\nLine 6\n'); expect(sourceAfterCut).not.toContain('Line 3'); expect(sourceAfterCut).not.toContain('Line 4'); expect(sourceAfterCut).not.toContain('Line 5'); // Step 2: Verify clipboard has cut content const clipboardResult = await clipboardTools.showClipboard(sessionId); expect(clipboardResult.hasContent).toBe(true); expect(clipboardResult.content).toBe('Line 3\nLine 4\nLine 5'); expect(clipboardResult.operationType).toBe('cut'); // Step 3: Paste to destination const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: destFile, target_line: 2 }, ]); expect(pasteResult.success).toBe(true); // Verify destination has pasted content const destAfterPaste = fs.readFileSync(destFile, 'utf-8'); const expectedDest = 'Dest Line 1\nLine 3\nLine 4\nLine 5\nDest Line 2\n'; expect(destAfterPaste).toBe(expectedDest); // Verify source still has lines removed const sourceFinal = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceFinal).toBe('Line 1\nLine 2\nLine 6\n'); }); test('should move code between files using cut-paste', async () => { // Setup: Create source file with function const sourceFile = path.join(testDir, 'old-module.js'); const sourceContent = `export function oldFunction() {\n return 'old';\n}\n\nexport function keepThis() {\n return 'keep';\n}\n`; fs.writeFileSync(sourceFile, sourceContent); // Setup: Create destination file const destFile = path.join(testDir, 'new-module.js'); const destContent = `// New module\n\n`; fs.writeFileSync(destFile, destContent); // Step 1: Cut the old function (lines 1-3) const cutResult = await clipboardTools.cutLines(sessionId, sourceFile, 1, 3); expect(cutResult.success).toBe(true); expect(cutResult.content).toContain('oldFunction'); // Step 2: Paste to new module const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: destFile, target_line: 2 }, ]); expect(pasteResult.success).toBe(true); // Verify source has function removed but kept other function const sourceFinal = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceFinal).not.toContain('oldFunction'); expect(sourceFinal).toContain('keepThis'); // Verify destination has new function const destFinal = fs.readFileSync(destFile, 'utf-8'); expect(destFinal).toContain('oldFunction'); expect(destFinal).toContain('// New module'); }); }); describe('Complex Multi-Operation Workflows', () => { test('should handle successive copy operations overwriting clipboard', async () => { // Setup files const file1 = path.join(testDir, 'file1.txt'); const file2 = path.join(testDir, 'file2.txt'); const file3 = path.join(testDir, 'file3.txt'); fs.writeFileSync(file1, 'File1 Line 1\nFile1 Line 2\n'); fs.writeFileSync(file2, 'File2 Line 1\nFile2 Line 2\n'); fs.writeFileSync(file3, 'File3 Line 1\n'); // Copy from file1 await clipboardTools.copyLines(sessionId, file1, 1, 1); // Verify clipboard has file1 content let clipboard = await clipboardTools.showClipboard(sessionId); expect(clipboard.content).toBe('File1 Line 1'); // Copy from file2 (should overwrite clipboard) await clipboardTools.copyLines(sessionId, file2, 1, 1); // Verify clipboard now has file2 content clipboard = await clipboardTools.showClipboard(sessionId); expect(clipboard.content).toBe('File2 Line 1'); expect(clipboard.sourceFile).toBe(file2); // Paste should use most recent clipboard const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: file3, target_line: 1 }, ]); expect(pasteResult.success).toBe(true); const file3Content = fs.readFileSync(file3, 'utf-8'); expect(file3Content).toContain('File2 Line 1'); expect(file3Content).not.toContain('File1 Line 1'); }); test('should handle paste to same file multiple times with adjusted line numbers', async () => { // Setup: Create source and target const sourceFile = path.join(testDir, 'source.txt'); const targetFile = path.join(testDir, 'target.txt'); fs.writeFileSync(sourceFile, 'Insert Line\n'); fs.writeFileSync(targetFile, 'Line 1\nLine 2\nLine 3\nLine 4\nLine 5\n'); // Copy the insert line await clipboardTools.copyLines(sessionId, sourceFile, 1, 1); // Paste to same file at multiple locations const pasteResult = await clipboardTools.pasteLines(sessionId, [ { file_path: targetFile, target_line: 2 }, // After Line 1 { file_path: targetFile, target_line: 4 }, // After Line 3 (but adjusted for previous insert) { file_path: targetFile, target_line: 6 }, // After Line 5 (adjusted) ]); expect(pasteResult.success).toBe(true); expect(pasteResult.pastedTo).toHaveLength(3); // Verify file has inserts at correct locations const finalContent = fs.readFileSync(targetFile, 'utf-8'); const lines = finalContent.split('\n').filter((l) => l.length > 0); expect(lines.length).toBe(8); // 5 original + 3 inserts expect(lines).toContain('Insert Line'); }); test('should maintain operation history across multiple operations', async () => { // Setup files const file1 = path.join(testDir, 'file1.txt'); const file2 = path.join(testDir, 'file2.txt'); fs.writeFileSync(file1, 'Line 1\nLine 2\nLine 3\n'); fs.writeFileSync(file2, 'Target Line 1\n'); // Perform multiple operations await clipboardTools.copyLines(sessionId, file1, 1, 1); // Op 1: copy await clipboardTools.pasteLines(sessionId, [{ file_path: file2, target_line: 1 }]); // Op 2: paste await clipboardTools.undoLastPaste(sessionId); // Op 3: undo await clipboardTools.cutLines(sessionId, file1, 2, 2); // Op 4: cut await clipboardTools.pasteLines(sessionId, [{ file_path: file2, target_line: 1 }]); // Op 5: paste // Get operation history const history = await clipboardTools.getOperationHistory(sessionId, 20); expect(history.operations.length).toBeGreaterThanOrEqual(5); // Verify operation types in history const opTypes = history.operations.map((op) => op.operationType).reverse(); expect(opTypes).toContain('copy'); expect(opTypes).toContain('paste'); expect(opTypes).toContain('undo'); expect(opTypes).toContain('cut'); }); }); describe('Error Recovery in Workflows', () => { test('should handle paste failure and maintain clipboard state', async () => { // Setup source file const sourceFile = path.join(testDir, 'source.txt'); fs.writeFileSync(sourceFile, 'Line 1\nLine 2\n'); // Copy content await clipboardTools.copyLines(sessionId, sourceFile, 1, 2); // Try to paste to non-existent file (should fail) const nonExistentFile = path.join(testDir, 'does-not-exist.txt'); await expect( clipboardTools.pasteLines(sessionId, [{ file_path: nonExistentFile, target_line: 1 }]) ).rejects.toThrow(); // Verify clipboard still has content after failed paste const clipboard = await clipboardTools.showClipboard(sessionId); expect(clipboard.hasContent).toBe(true); expect(clipboard.content).toBe('Line 1\nLine 2'); }); test('should handle undo when no paste exists', async () => { // Try to undo without any paste operation await expect(clipboardTools.undoLastPaste(sessionId)).rejects.toThrow( 'No paste operation to undo' ); }); test('should handle paste with empty clipboard', async () => { // Setup destination file const destFile = path.join(testDir, 'dest.txt'); fs.writeFileSync(destFile, 'Line 1\n'); // Try to paste without copying anything first await expect( clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]) ).rejects.toThrow('Clipboard is empty'); }); }); });

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/Pr0j3c7t0dd-Ltd/cut-copy-paste-mcp'

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