Skip to main content
Glama

Cut-Copy-Paste Clipboard Server

cut-paste-undo.test.ts•9.39 kB
/** * Cut-Paste-Undo Integration Tests * * Tests for ensuring that undoing a paste operation that came from a cut * properly restores both the paste target AND the cut source. */ 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('Cut-Paste-Undo Workflow', () => { 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(); }); test('should restore both target and source when undoing a paste from cut', async () => { // Setup: Create source file with 5 lines 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\n'; fs.writeFileSync(destFile, destContent); // Step 1: Cut lines 2-4 from source const cutResult = await clipboardTools.cutLines(sessionId, sourceFile, 2, 4); expect(cutResult.success).toBe(true); expect(cutResult.lines).toEqual(['Line 2', 'Line 3', 'Line 4']); // Verify source file has lines removed const sourceAfterCut = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterCut).toBe('Line 1\nLine 5\n'); // Step 2: 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'); expect(destAfterPaste).toBe('Dest Line 1\nLine 2\nLine 3\nLine 4\nDest Line 2\n'); // Verify source still has lines removed const sourceAfterPaste = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterPaste).toBe('Line 1\nLine 5\n'); // Step 3: Undo the paste const undoResult = await clipboardTools.undoLastPaste(sessionId); expect(undoResult.success).toBe(true); expect(undoResult.restoredFiles.length).toBeGreaterThanOrEqual(1); // CRITICAL ASSERTIONS: // 1. Destination should be restored to original state const destAfterUndo = fs.readFileSync(destFile, 'utf-8'); expect(destAfterUndo).toBe(destContent); // 2. Source should be restored to original state (lines 2-4 restored) const sourceAfterUndo = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterUndo).toBe(sourceContent); }); test('should NOT restore source when undoing a paste from copy', 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 destination file const destFile = path.join(testDir, 'destination.txt'); const destContent = 'Dest Line 1\nDest Line 2\n'; fs.writeFileSync(destFile, destContent); // Step 1: COPY (not cut) lines from source await clipboardTools.copyLines(sessionId, sourceFile, 2, 3); // Verify source unchanged const sourceAfterCopy = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterCopy).toBe(sourceContent); // Step 2: Paste to destination await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 2 }]); // Step 3: Undo the paste await clipboardTools.undoLastPaste(sessionId); // Destination should be restored const destAfterUndo = fs.readFileSync(destFile, 'utf-8'); expect(destAfterUndo).toBe(destContent); // Source should STILL be unchanged (since it was a copy, not a cut) const sourceAfterUndo = fs.readFileSync(sourceFile, 'utf-8'); expect(sourceAfterUndo).toBe(sourceContent); }); test('should restore source file when undoing multi-target paste from cut', 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'); fs.writeFileSync(dest1, 'Dest1\n'); fs.writeFileSync(dest2, 'Dest2\n'); const dest1Content = fs.readFileSync(dest1, 'utf-8'); const dest2Content = fs.readFileSync(dest2, 'utf-8'); // Step 1: Cut line 2 from source await clipboardTools.cutLines(sessionId, sourceFile, 2, 2); // Verify source has line removed expect(fs.readFileSync(sourceFile, 'utf-8')).toBe('Line 1\nLine 3\n'); // Step 2: Paste to multiple destinations await clipboardTools.pasteLines(sessionId, [ { file_path: dest1, target_line: 1 }, { file_path: dest2, target_line: 1 }, ]); // Verify both destinations have pasted content expect(fs.readFileSync(dest1, 'utf-8')).toContain('Line 2'); expect(fs.readFileSync(dest2, 'utf-8')).toContain('Line 2'); // Step 3: Undo await clipboardTools.undoLastPaste(sessionId); // All three files should be restored expect(fs.readFileSync(sourceFile, 'utf-8')).toBe(sourceContent); expect(fs.readFileSync(dest1, 'utf-8')).toBe(dest1Content); expect(fs.readFileSync(dest2, 'utf-8')).toBe(dest2Content); }); test('should handle sequential cut-paste-undo-paste operations correctly', async () => { // Setup files const sourceFile = path.join(testDir, 'source.txt'); const destFile = path.join(testDir, 'dest.txt'); const sourceContent = 'Line 1\nLine 2\nLine 3\n'; const destContent = 'Dest\n'; fs.writeFileSync(sourceFile, sourceContent); fs.writeFileSync(destFile, destContent); // Operation 1: Cut and paste await clipboardTools.cutLines(sessionId, sourceFile, 2, 2); expect(fs.readFileSync(sourceFile, 'utf-8')).toBe('Line 1\nLine 3\n'); await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]); expect(fs.readFileSync(destFile, 'utf-8')).toContain('Line 2'); // Operation 2: Undo await clipboardTools.undoLastPaste(sessionId); expect(fs.readFileSync(sourceFile, 'utf-8')).toBe(sourceContent); // Source restored expect(fs.readFileSync(destFile, 'utf-8')).toBe(destContent); // Dest restored // Operation 3: Paste again (clipboard should still have Line 2) await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]); // Now we should have the content pasted again expect(fs.readFileSync(destFile, 'utf-8')).toContain('Line 2'); // But source should still be original (because we're pasting from clipboard, not cutting again) // NOTE: This is a design decision - after undo restores source, clipboard still has content // but paste doesn't re-cut from source }); test('should track cut operation ID when logging paste', async () => { // This test verifies the data model links paste to source cut operation const sourceFile = path.join(testDir, 'source.txt'); const destFile = path.join(testDir, 'dest.txt'); fs.writeFileSync(sourceFile, 'Line 1\nLine 2\n'); fs.writeFileSync(destFile, 'Dest\n'); // Cut from source await clipboardTools.cutLines(sessionId, sourceFile, 1, 1); // Paste to dest await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]); // Get operation history const history = await clipboardTools.getOperationHistory(sessionId, 10); // Should have cut and paste operations const opTypes = history.operations.map((op) => op.operationType); expect(opTypes).toContain('cut'); expect(opTypes).toContain('paste'); // The cut operation should have source file metadata const cutOp = history.operations.find((op) => op.operationType === 'cut'); expect(cutOp).toBeDefined(); expect(cutOp?.details.sourceFile).toBe(sourceFile); expect(cutOp?.details.startLine).toBe(1); expect(cutOp?.details.endLine).toBe(1); }); });

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