test-negative-offset-readfile.jsโข9.4 kB
/**
* Test script for negative offset handling in read_file
*
* This script tests:
* 1. Whether negative offsets work correctly (like Unix tail)
* 2. How the tool handles edge cases with negative offsets
* 3. Comparison with positive offset behavior
* 4. Error handling for invalid parameters
*/
import { configManager } from '../dist/config-manager.js';
import { handleReadFile } from '../dist/handlers/filesystem-handlers.js';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import assert from 'assert';
// Get directory name
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Define test paths
const TEST_FILE = path.join(__dirname, 'test-negative-offset.txt');
/**
* Setup function to prepare test environment
*/
async function setup() {
console.log('๐ง Setting up negative offset test...');
// Save original config to restore later
const originalConfig = await configManager.getConfig();
// Set allowed directories to include test directory
await configManager.setValue('allowedDirectories', [__dirname]);
// Create test file with numbered lines for easy verification
const testLines = [];
for (let i = 1; i <= 50; i++) {
testLines.push(`Line ${i}: This is line number ${i} in the test file.`);
}
const testContent = testLines.join('\n');
await fs.writeFile(TEST_FILE, testContent, 'utf8');
console.log(`โ Created test file with 50 lines: ${TEST_FILE}`);
return originalConfig;
}
/**
* Teardown function to clean up after tests
*/
async function teardown(originalConfig) {
console.log('๐งน Cleaning up test environment...');
// Reset configuration to original
await configManager.updateConfig(originalConfig);
// Remove test file
try {
await fs.rm(TEST_FILE, { force: true });
console.log('โ Test file cleaned up');
} catch (error) {
console.log('โ ๏ธ Warning: Could not clean up test file:', error.message);
}
}
/**
* Test negative offset functionality
*/
async function testNegativeOffset() {
console.log('\n๐ Testing negative offset behavior...');
const tests = [
{
name: 'Negative offset -10 (last 10 lines)',
args: { path: TEST_FILE, offset: -10, length: 20 },
expectLines: ['Line 41:', 'Line 42:', 'Line 43:', 'Line 44:', 'Line 45:', 'Line 46:', 'Line 47:', 'Line 48:', 'Line 49:', 'Line 50:']
},
{
name: 'Negative offset -5 (last 5 lines)',
args: { path: TEST_FILE, offset: -5, length: 10 },
expectLines: ['Line 46:', 'Line 47:', 'Line 48:', 'Line 49:', 'Line 50:']
},
{
name: 'Negative offset -1 (last 1 line)',
args: { path: TEST_FILE, offset: -1, length: 5 },
expectLines: ['Line 50:']
},
{
name: 'Large negative offset -100 (beyond file size)',
args: { path: TEST_FILE, offset: -100, length: 10 },
expectLines: ['Line 1:', 'Line 2:', 'Line 3:', 'Line 4:', 'Line 5:', 'Line 6:', 'Line 7:', 'Line 8:', 'Line 9:', 'Line 10:']
}
];
let passedTests = 0;
for (const test of tests) {
console.log(`\n ๐งช ${test.name}`);
try {
const result = await handleReadFile(test.args);
if (result.isError) {
console.log(` โ Error: ${result.content[0].text}`);
continue;
}
const content = result.content[0].text;
console.log(` ๐ Result (first 200 chars): ${content.substring(0, 200)}...`);
// Check if expected lines are present
let foundExpected = 0;
for (const expectedLine of test.expectLines) {
if (content.includes(expectedLine)) {
foundExpected++;
}
}
if (foundExpected === test.expectLines.length) {
console.log(` โ
PASS: Found all ${foundExpected} expected lines`);
passedTests++;
} else {
console.log(` โ FAIL: Found only ${foundExpected}/${test.expectLines.length} expected lines`);
console.log(` Expected: ${test.expectLines.join(', ')}`);
}
} catch (error) {
console.log(` โ Exception: ${error.message}`);
}
}
return passedTests === tests.length;
}
/**
* Test comparison between negative and positive offsets
*/
async function testOffsetComparison() {
console.log('\n๐ Testing offset comparison (negative vs positive)...');
try {
// Test reading last 5 lines with negative offset
const negativeResult = await handleReadFile({
path: TEST_FILE,
offset: -5,
length: 10
});
// Test reading same lines with positive offset (45 to get last 5 lines of 50)
const positiveResult = await handleReadFile({
path: TEST_FILE,
offset: 45,
length: 5
});
if (negativeResult.isError || positiveResult.isError) {
console.log(' โ One or both requests failed');
return false;
}
const negativeContent = negativeResult.content[0].text;
const positiveContent = positiveResult.content[0].text;
console.log(' ๐ Negative offset result:');
console.log(` ${negativeContent.split('\n').slice(2, 4).join('\\n')}`); // Skip header lines
console.log(' ๐ Positive offset result:');
console.log(` ${positiveContent.split('\n').slice(2, 4).join('\\n')}`); // Skip header lines
// Extract actual content lines (skip informational headers)
const negativeLines = negativeContent.split('\n').filter(line => line.startsWith('Line '));
const positiveLines = positiveContent.split('\n').filter(line => line.startsWith('Line '));
const isMatching = negativeLines.join('\\n') === positiveLines.join('\\n');
if (isMatching) {
console.log(' โ
PASS: Negative and positive offsets return same content');
return true;
} else {
console.log(' โ FAIL: Negative and positive offsets return different content');
console.log(` Negative: ${negativeLines.slice(0, 2).join(', ')}`);
console.log(` Positive: ${positiveLines.slice(0, 2).join(', ')}`);
return false;
}
} catch (error) {
console.log(` โ Exception during comparison: ${error.message}`);
return false;
}
}
/**
* Test edge cases and error handling
*/
async function testEdgeCases() {
console.log('\n๐ Testing edge cases...');
const edgeTests = [
{
name: 'Zero offset with length',
args: { path: TEST_FILE, offset: 0, length: 3 },
shouldPass: true
},
{
name: 'Very large negative offset',
args: { path: TEST_FILE, offset: -1000, length: 5 },
shouldPass: true // Should handle gracefully
},
{
name: 'Negative offset with zero length',
args: { path: TEST_FILE, offset: -5, length: 0 },
shouldPass: true // Should return empty or minimal content
}
];
let passedEdgeTests = 0;
for (const test of edgeTests) {
console.log(`\n ๐งช ${test.name}`);
try {
const result = await handleReadFile(test.args);
if (result.isError && test.shouldPass) {
console.log(` โ Unexpected error: ${result.content[0].text}`);
} else if (!result.isError && test.shouldPass) {
console.log(` โ
PASS: Handled gracefully`);
console.log(` ๐ Result length: ${result.content[0].text.length} chars`);
passedEdgeTests++;
} else if (result.isError && !test.shouldPass) {
console.log(` โ
PASS: Expected error occurred`);
passedEdgeTests++;
}
} catch (error) {
if (test.shouldPass) {
console.log(` โ Unexpected exception: ${error.message}`);
} else {
console.log(` โ
PASS: Expected exception occurred`);
passedEdgeTests++;
}
}
}
return passedEdgeTests === edgeTests.length;
}
/**
* Main test runner
*/
async function runAllTests() {
console.log('๐งช Starting negative offset read_file tests...\n');
let originalConfig;
let allTestsPassed = true;
try {
originalConfig = await setup();
// Run all test suites
const negativeOffsetPassed = await testNegativeOffset();
const comparisonPassed = await testOffsetComparison();
const edgeCasesPassed = await testEdgeCases();
allTestsPassed = negativeOffsetPassed && comparisonPassed && edgeCasesPassed;
console.log('\n๐ Test Results Summary:');
console.log(` Negative offset tests: ${negativeOffsetPassed ? 'โ
PASS' : 'โ FAIL'}`);
console.log(` Comparison tests: ${comparisonPassed ? 'โ
PASS' : 'โ FAIL'}`);
console.log(` Edge case tests: ${edgeCasesPassed ? 'โ
PASS' : 'โ FAIL'}`);
console.log(`\n๐ฏ Overall result: ${allTestsPassed ? 'โ
ALL TESTS PASSED!' : 'โ SOME TESTS FAILED'}`);
} catch (error) {
console.error('โ Test setup/execution failed:', error.message);
allTestsPassed = false;
} finally {
if (originalConfig) {
await teardown(originalConfig);
}
}
return allTestsPassed;
}
// Export the main test function
export default runAllTests;
// If this file is run directly (not imported), execute the test
if (import.meta.url === `file://${process.argv[1]}`) {
runAllTests().then(success => {
process.exit(success ? 0 : 1);
}).catch(error => {
console.error('โ Unhandled error:', error);
process.exit(1);
});
}