Skip to main content
Glama
test-excel-files.js11.6 kB
/** * Test script for Excel file handling functionality * * This script tests the ExcelFileHandler implementation: * 1. Reading Excel files (basic, sheet selection, range, offset/length) * 2. Writing Excel files (single sheet, multiple sheets, append mode) * 3. Editing Excel files (range updates) * 4. Getting Excel file info (sheet metadata) * 5. File handler factory (correct handler selection) */ import { configManager } from '../dist/config-manager.js'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import assert from 'assert'; import { readFile, writeFile, getFileInfo } from '../dist/tools/filesystem.js'; import { handleEditBlock } from '../dist/handlers/edit-search-handlers.js'; import { getFileHandler } from '../dist/utils/files/factory.js'; // Get directory name const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Define test directory and files const TEST_DIR = path.join(__dirname, 'test_excel_files'); const BASIC_EXCEL = path.join(TEST_DIR, 'basic.xlsx'); const MULTI_SHEET_EXCEL = path.join(TEST_DIR, 'multi_sheet.xlsx'); const EDIT_EXCEL = path.join(TEST_DIR, 'edit_test.xlsx'); /** * Helper function to clean up test directories */ async function cleanupTestDirectories() { try { await fs.rm(TEST_DIR, { recursive: true, force: true }); } catch (error) { if (error.code !== 'ENOENT') { console.error('Error during cleanup:', error); } } } /** * Setup function to prepare the test environment */ async function setup() { // Clean up before tests (in case previous run left files) await cleanupTestDirectories(); // Create test directory await fs.mkdir(TEST_DIR, { recursive: true }); console.log(`✓ Setup: created test directory: ${TEST_DIR}`); // Save original config to restore later const originalConfig = await configManager.getConfig(); // Set allowed directories to include our test directory await configManager.setValue('allowedDirectories', [TEST_DIR]); console.log(`✓ Setup: set allowed directories`); return originalConfig; } /** * Teardown function to clean up after tests */ async function teardown(originalConfig) { // Reset configuration to original if (originalConfig) { await configManager.updateConfig(originalConfig); } await cleanupTestDirectories(); console.log('✓ Teardown: test directory cleaned up and config restored'); } /** * Test 1: File handler factory selects ExcelFileHandler for .xlsx files */ async function testFileHandlerFactory() { console.log('\n--- Test 1: File Handler Factory ---'); const handler = await getFileHandler('test.xlsx'); assert.ok(handler, 'Handler should be returned for .xlsx file'); assert.ok(handler.constructor.name === 'ExcelFileHandler', `Expected ExcelFileHandler but got ${handler.constructor.name}`); const txtHandler = await getFileHandler('test.txt'); assert.ok(txtHandler.constructor.name === 'TextFileHandler', `Expected TextFileHandler for .txt but got ${txtHandler.constructor.name}`); console.log('✓ File handler factory correctly selects handlers'); } /** * Test 2: Write and read basic Excel file */ async function testBasicWriteRead() { console.log('\n--- Test 2: Basic Write and Read ---'); // Write a simple Excel file const data = JSON.stringify([ ['Name', 'Age', 'City'], ['Alice', 30, 'New York'], ['Bob', 25, 'Los Angeles'], ['Charlie', 35, 'Chicago'] ]); await writeFile(BASIC_EXCEL, data); console.log('✓ Wrote basic Excel file'); // Read it back const result = await readFile(BASIC_EXCEL); assert.ok(result.content, 'Should have content'); // Excel handler returns application/json because content is JSON-formatted for LLM consumption assert.ok(result.mimeType === 'application/json', `Expected application/json mime type but got ${result.mimeType}`); // Verify content contains our data const content = result.content.toString(); assert.ok(content.includes('Name'), 'Content should include Name header'); assert.ok(content.includes('Alice'), 'Content should include Alice'); assert.ok(content.includes('Chicago'), 'Content should include Chicago'); console.log('✓ Read back Excel file with correct content'); } /** * Test 3: Write and read multi-sheet Excel file */ async function testMultiSheetWriteRead() { console.log('\n--- Test 3: Multi-Sheet Write and Read ---'); // Write multi-sheet Excel file const data = JSON.stringify({ 'Employees': [ ['Name', 'Department'], ['Alice', 'Engineering'], ['Bob', 'Sales'] ], 'Departments': [ ['Name', 'Budget'], ['Engineering', 100000], ['Sales', 50000] ] }); await writeFile(MULTI_SHEET_EXCEL, data); console.log('✓ Wrote multi-sheet Excel file'); // Read specific sheet by name const result1 = await readFile(MULTI_SHEET_EXCEL, { sheet: 'Employees' }); const content1 = result1.content.toString(); assert.ok(content1.includes('Alice'), 'Employees sheet should contain Alice'); assert.ok(content1.includes('Engineering'), 'Employees sheet should contain Engineering'); console.log('✓ Read Employees sheet by name'); // Read specific sheet by index const result2 = await readFile(MULTI_SHEET_EXCEL, { sheet: 1 }); const content2 = result2.content.toString(); assert.ok(content2.includes('Budget'), 'Departments sheet should contain Budget'); assert.ok(content2.includes('100000'), 'Departments sheet should contain 100000'); console.log('✓ Read Departments sheet by index'); } /** * Test 4: Read with range parameter */ async function testRangeRead() { console.log('\n--- Test 4: Range Read ---'); // Use the basic file we created const result = await readFile(BASIC_EXCEL, { sheet: 'Sheet1', range: 'A1:B2' }); const content = result.content.toString(); // Should only have first 2 rows and 2 columns assert.ok(content.includes('Name'), 'Range should include Name'); assert.ok(content.includes('Age'), 'Range should include Age'); assert.ok(content.includes('Alice'), 'Range should include Alice'); // City is column C, should NOT be included assert.ok(!content.includes('City') || content.split('City').length === 1, 'Range A1:B2 should not include City column'); console.log('✓ Range read returns correct subset of data'); } /** * Test 5: Read with offset and length */ async function testOffsetLengthRead() { console.log('\n--- Test 5: Offset and Length Read ---'); // Read with offset (skip header) const result = await readFile(BASIC_EXCEL, { offset: 1, length: 2 }); const content = result.content.toString(); // Should have rows 2-3 (Alice, Bob) but not header or Charlie assert.ok(content.includes('Alice'), 'Should include Alice (row 2)'); assert.ok(content.includes('Bob'), 'Should include Bob (row 3)'); console.log('✓ Offset and length read works correctly'); } /** * Test 6: Edit Excel range */ async function testEditRange() { console.log('\n--- Test 6: Edit Excel Range ---'); // Create a file to edit const data = JSON.stringify([ ['Product', 'Price'], ['Apple', 1.00], ['Banana', 0.50], ['Cherry', 2.00] ]); await writeFile(EDIT_EXCEL, data); console.log('✓ Created file for editing'); // Edit a cell using edit_block with range const editResult = await handleEditBlock({ file_path: EDIT_EXCEL, range: 'Sheet1!B2', content: [[1.50]] // Update Apple price }); assert.ok(!editResult.isError, `Edit should succeed: ${editResult.content?.[0]?.text}`); console.log('✓ Edit range succeeded'); // Verify the edit const readResult = await readFile(EDIT_EXCEL); const content = readResult.content.toString(); assert.ok(content.includes('1.5'), 'Price should be updated to 1.50'); console.log('✓ Edit was persisted correctly'); } /** * Test 7: Get Excel file info */ async function testGetFileInfo() { console.log('\n--- Test 7: Get File Info ---'); const info = await getFileInfo(MULTI_SHEET_EXCEL); assert.ok(info.isExcelFile, 'Should be marked as Excel file'); assert.ok(info.sheets, 'Should have sheets info'); assert.ok(Array.isArray(info.sheets), 'Sheets should be an array'); assert.strictEqual(info.sheets.length, 2, 'Should have 2 sheets'); // Check sheet details const sheetNames = info.sheets.map(s => s.name); assert.ok(sheetNames.includes('Employees'), 'Should have Employees sheet'); assert.ok(sheetNames.includes('Departments'), 'Should have Departments sheet'); // Check row/column counts const employeesSheet = info.sheets.find(s => s.name === 'Employees'); assert.ok(employeesSheet.rowCount >= 3, 'Employees sheet should have at least 3 rows'); assert.ok(employeesSheet.colCount >= 2, 'Employees sheet should have at least 2 columns'); console.log('✓ File info returns correct sheet metadata'); } /** * Test 8: Append mode */ async function testAppendMode() { console.log('\n--- Test 8: Append Mode ---'); // Create initial file const initialData = JSON.stringify([ ['Name', 'Score'], ['Alice', 100] ]); await writeFile(BASIC_EXCEL, initialData); // Append more data const appendData = JSON.stringify([ ['Bob', 95], ['Charlie', 88] ]); await writeFile(BASIC_EXCEL, appendData, 'append'); console.log('✓ Appended data to Excel file'); // Read and verify const result = await readFile(BASIC_EXCEL); const content = result.content.toString(); assert.ok(content.includes('Alice'), 'Should still have Alice'); assert.ok(content.includes('Bob'), 'Should have appended Bob'); assert.ok(content.includes('Charlie'), 'Should have appended Charlie'); console.log('✓ Append mode works correctly'); } /** * Test 9: Negative offset (read from end) */ async function testNegativeOffset() { console.log('\n--- Test 9: Negative Offset (Tail) ---'); // Create file with multiple rows const data = JSON.stringify([ ['Row', 'Value'], ['1', 'First'], ['2', 'Second'], ['3', 'Third'], ['4', 'Fourth'], ['5', 'Fifth'] ]); await writeFile(BASIC_EXCEL, data); // Read last 2 rows const result = await readFile(BASIC_EXCEL, { offset: -2 }); const content = result.content.toString(); assert.ok(content.includes('Fourth') || content.includes('Fifth'), 'Should include data from last rows'); console.log('✓ Negative offset reads from end'); } /** * Run all tests */ async function runAllTests() { console.log('=== Excel File Handling Tests ===\n'); await testFileHandlerFactory(); await testBasicWriteRead(); await testMultiSheetWriteRead(); await testRangeRead(); await testOffsetLengthRead(); await testEditRange(); await testGetFileInfo(); await testAppendMode(); await testNegativeOffset(); console.log('\n✅ All Excel tests passed!'); } // Export the main test function export default async function runTests() { let originalConfig; try { originalConfig = await setup(); await runAllTests(); } catch (error) { console.error('❌ Test failed:', error.message); console.error(error.stack); return false; } finally { if (originalConfig) { await teardown(originalConfig); } } return true; } // If this file is run directly, execute the test if (import.meta.url === `file://${process.argv[1]}`) { runTests().then(success => { process.exit(success ? 0 : 1); }).catch(error => { console.error('❌ Unhandled error:', error); process.exit(1); }); }

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/wonderwhy-er/DesktopCommanderMCP'

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