Skip to main content
Glama

Cut-Copy-Paste Clipboard Server

edge-cases.test.tsโ€ข14.2 kB
/** * Edge Case Tests * * Tests for edge cases and boundary conditions: * - Very large files * - Binary files (should reject) * - Rapid successive operations * - Concurrent operations * - Invalid inputs */ 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('Edge Case Tests', () => { 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(() => { 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 ); sessionId = sessionManager.createSession(); testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mcp-clipboard-edge-')); }); afterEach(() => { if (fs.existsSync(testDir)) { fs.rmSync(testDir, { recursive: true, force: true }); } dbManager.close(); }); describe('Large File Handling', () => { test('should handle copying from large files (10,000 lines)', async () => { // Create a large file with 10,000 lines const largeFile = path.join(testDir, 'large.txt'); const lines = Array.from({ length: 10000 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(largeFile, lines.join('\n') + '\n'); // Copy a range from middle of large file const startTime = Date.now(); const result = await clipboardTools.copyLines(sessionId, largeFile, 5000, 5100); const endTime = Date.now(); expect(result.success).toBe(true); expect(result.lines).toHaveLength(101); expect(result.lines[0]).toBe('Line 5000'); expect(result.lines[100]).toBe('Line 5100'); // Should complete in reasonable time (< 1 second) expect(endTime - startTime).toBeLessThan(1000); }); test('should handle pasting to large files', async () => { // Create source with content to paste const sourceFile = path.join(testDir, 'source.txt'); fs.writeFileSync(sourceFile, 'Insert Line 1\nInsert Line 2\n'); // Create large target file const largeFile = path.join(testDir, 'large-target.txt'); const lines = Array.from({ length: 5000 }, (_, i) => `Line ${i + 1}`); fs.writeFileSync(largeFile, lines.join('\n') + '\n'); // Copy and paste await clipboardTools.copyLines(sessionId, sourceFile, 1, 2); const startTime = Date.now(); const result = await clipboardTools.pasteLines(sessionId, [ { file_path: largeFile, target_line: 2500 }, ]); const endTime = Date.now(); expect(result.success).toBe(true); // Verify paste happened at correct location const content = fs.readFileSync(largeFile, 'utf-8'); expect(content).toContain('Insert Line 1'); expect(content).toContain('Insert Line 2'); // Should complete in reasonable time (< 2 seconds) expect(endTime - startTime).toBeLessThan(2000); }); test('should reject copying very large ranges that exceed size limits', async () => { // Create a file with many large lines const largeFile = path.join(testDir, 'huge.txt'); // Each line is 100KB, 200 lines = 20MB (exceeds 10MB limit) const hugeLine = 'A'.repeat(100000); const lines = Array.from({ length: 200 }, () => hugeLine); fs.writeFileSync(largeFile, lines.join('\n') + '\n'); // Try to copy all 200 lines (20MB) - should reject await expect(clipboardTools.copyLines(sessionId, largeFile, 1, 200)).rejects.toThrow(); }); }); describe('Binary File Handling', () => { test('should reject binary files (PNG image)', async () => { // Create a fake binary file (PNG header) const binaryFile = path.join(testDir, 'image.png'); const pngHeader = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); fs.writeFileSync(binaryFile, pngHeader); // Try to copy from binary file - should reject await expect(clipboardTools.copyLines(sessionId, binaryFile, 1, 1)).rejects.toThrow(); }); test('should reject binary files (PDF)', async () => { // Create a fake PDF file const pdfFile = path.join(testDir, 'document.pdf'); const pdfHeader = Buffer.from('%PDF-1.4\n'); fs.writeFileSync(pdfFile, pdfHeader); // Try to copy from PDF - should reject await expect(clipboardTools.copyLines(sessionId, pdfFile, 1, 1)).rejects.toThrow(); }); test('should handle text files with unicode characters', async () => { // Create file with various unicode characters const unicodeFile = path.join(testDir, 'unicode.txt'); const unicodeContent = 'ไฝ ๅฅฝไธ–็•Œ\nะŸั€ะธะฒะตั‚ ะผะธั€\n๐ŸŽ‰ Emoji test\nCafรฉ rรฉsumรฉ\n'; fs.writeFileSync(unicodeFile, unicodeContent, 'utf-8'); // Should successfully copy unicode content const result = await clipboardTools.copyLines(sessionId, unicodeFile, 1, 4); expect(result.success).toBe(true); expect(result.content).toContain('ไฝ ๅฅฝไธ–็•Œ'); expect(result.content).toContain('ะŸั€ะธะฒะตั‚ ะผะธั€'); expect(result.content).toContain('๐ŸŽ‰'); expect(result.content).toContain('Cafรฉ rรฉsumรฉ'); }); }); describe('Rapid Successive Operations', () => { test('should handle rapid copy operations', async () => { // Create multiple source files const files = Array.from({ length: 10 }, (_, i) => { const filePath = path.join(testDir, `file${i}.txt`); fs.writeFileSync(filePath, `Content ${i}\n`); return filePath; }); // Rapidly copy from each file const results = await Promise.all( files.map((file) => clipboardTools.copyLines(sessionId, file, 1, 1)) ); // All should succeed results.forEach((result) => { expect(result.success).toBe(true); }); // Clipboard should have last copied content const clipboard = await clipboardTools.showClipboard(sessionId); expect(clipboard.content).toBe('Content 9'); }); test('should handle rapid copy-paste-undo cycles', async () => { // Setup files const sourceFile = path.join(testDir, 'source.txt'); const destFile = path.join(testDir, 'dest.txt'); fs.writeFileSync(sourceFile, 'Line 1\nLine 2\n'); const originalDest = 'Original\n'; fs.writeFileSync(destFile, originalDest); // Perform rapid copy-paste-undo cycles for (let i = 0; i < 5; i++) { // Copy await clipboardTools.copyLines(sessionId, sourceFile, 1, 2); // Paste await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]); // Verify paste let content = fs.readFileSync(destFile, 'utf-8'); expect(content).toContain('Line 1'); // Undo await clipboardTools.undoLastPaste(sessionId); // Verify undo content = fs.readFileSync(destFile, 'utf-8'); expect(content).toBe(originalDest); } // Final state should be original const finalContent = fs.readFileSync(destFile, 'utf-8'); expect(finalContent).toBe(originalDest); }); }); describe('Invalid Input Handling', () => { test('should reject invalid line ranges (start > end)', async () => { const file = path.join(testDir, 'test.txt'); fs.writeFileSync(file, 'Line 1\nLine 2\nLine 3\n'); await expect(clipboardTools.copyLines(sessionId, file, 3, 1)).rejects.toThrow(); }); test('should reject negative line numbers', async () => { const file = path.join(testDir, 'test.txt'); fs.writeFileSync(file, 'Line 1\nLine 2\n'); await expect(clipboardTools.copyLines(sessionId, file, -1, 2)).rejects.toThrow(); }); test('should reject line numbers beyond file length', async () => { const file = path.join(testDir, 'test.txt'); fs.writeFileSync(file, 'Line 1\nLine 2\n'); await expect(clipboardTools.copyLines(sessionId, file, 1, 100)).rejects.toThrow(); }); test('should reject invalid session ID', async () => { const file = path.join(testDir, 'test.txt'); fs.writeFileSync(file, 'Line 1\n'); await expect(clipboardTools.copyLines('invalid-session-id', file, 1, 1)).rejects.toThrow( 'Invalid session' ); }); test('should handle non-existent files gracefully', async () => { const nonExistentFile = path.join(testDir, 'does-not-exist.txt'); await expect(clipboardTools.copyLines(sessionId, nonExistentFile, 1, 1)).rejects.toThrow(); }); }); describe('Edge Cases in File Operations', () => { test('should handle empty files', async () => { const emptyFile = path.join(testDir, 'empty.txt'); fs.writeFileSync(emptyFile, ''); await expect(clipboardTools.copyLines(sessionId, emptyFile, 1, 1)).rejects.toThrow(); }); test('should handle files with only newlines', async () => { const newlineFile = path.join(testDir, 'newlines.txt'); fs.writeFileSync(newlineFile, '\n\n\n'); const result = await clipboardTools.copyLines(sessionId, newlineFile, 1, 3); expect(result.success).toBe(true); expect(result.lines).toHaveLength(3); expect(result.lines.every((line) => line === '')).toBe(true); }); test('should handle very long single line', async () => { const longLineFile = path.join(testDir, 'long-line.txt'); const longLine = 'A'.repeat(50000); // 50KB single line fs.writeFileSync(longLineFile, longLine + '\n'); const result = await clipboardTools.copyLines(sessionId, longLineFile, 1, 1); expect(result.success).toBe(true); expect(result.lines[0].length).toBe(50000); }); test('should preserve different line endings (CRLF)', async () => { const crlfFile = path.join(testDir, 'crlf.txt'); fs.writeFileSync(crlfFile, 'Line 1\r\nLine 2\r\nLine 3\r\n'); const result = await clipboardTools.copyLines(sessionId, crlfFile, 1, 3); expect(result.success).toBe(true); // Paste to new file const destFile = path.join(testDir, 'dest-crlf.txt'); fs.writeFileSync(destFile, 'Original\r\n'); await clipboardTools.pasteLines(sessionId, [{ file_path: destFile, target_line: 1 }]); // Line endings should be preserved const destContent = fs.readFileSync(destFile, 'utf-8'); expect(destContent).toContain('\r\n'); }); test('should handle paste at line 0 (beginning of file)', async () => { const sourceFile = path.join(testDir, 'source.txt'); const destFile = path.join(testDir, 'dest.txt'); fs.writeFileSync(sourceFile, 'Insert\n'); fs.writeFileSync(destFile, 'Line 1\nLine 2\n'); await clipboardTools.copyLines(sessionId, sourceFile, 1, 1); // Paste at line 0 (should insert at very beginning) const result = await clipboardTools.pasteLines(sessionId, [ { file_path: destFile, target_line: 0 }, ]); expect(result.success).toBe(true); const content = fs.readFileSync(destFile, 'utf-8'); expect(content.startsWith('Insert\n')).toBe(true); }); }); describe('Clipboard Size Limits', () => { test('should enforce 10MB clipboard limit', async () => { const largeFile = path.join(testDir, 'large-content.txt'); // Create file with ~11MB of content (exceeds 10MB limit) const largeLine = 'X'.repeat(100000); // 100KB per line const lines = Array.from({ length: 110 }, () => largeLine); // 11MB total fs.writeFileSync(largeFile, lines.join('\n') + '\n'); // Try to copy all lines - should reject due to size limit await expect(clipboardTools.copyLines(sessionId, largeFile, 1, 110)).rejects.toThrow(); }); test('should accept content just under 10MB limit', async () => { const largeFile = path.join(testDir, 'large-content.txt'); // Create file with ~9MB of content (under 10MB limit) const largeLine = 'Y'.repeat(100000); // 100KB per line const lines = Array.from({ length: 90 }, () => largeLine); // 9MB total fs.writeFileSync(largeFile, lines.join('\n') + '\n'); // Should succeed const result = await clipboardTools.copyLines(sessionId, largeFile, 1, 90); expect(result.success).toBe(true); expect(result.lines).toHaveLength(90); }); }); describe('Session Isolation', () => { test('should maintain separate clipboards for different sessions', async () => { // Create second session const sessionId2 = sessionManager.createSession(); // Create files const file1 = path.join(testDir, 'file1.txt'); const file2 = path.join(testDir, 'file2.txt'); fs.writeFileSync(file1, 'Session 1 Content\n'); fs.writeFileSync(file2, 'Session 2 Content\n'); // Session 1 copies file1 await clipboardTools.copyLines(sessionId, file1, 1, 1); // Session 2 copies file2 await clipboardTools.copyLines(sessionId2, file2, 1, 1); // Verify session 1 clipboard const clipboard1 = await clipboardTools.showClipboard(sessionId); expect(clipboard1.content).toBe('Session 1 Content'); // Verify session 2 clipboard const clipboard2 = await clipboardTools.showClipboard(sessionId2); expect(clipboard2.content).toBe('Session 2 Content'); // Clipboards should be independent expect(clipboard1.content).not.toBe(clipboard2.content); }); }); });

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