This commit is contained in:
144
packages/agent/tests/enhanced-read-file.test.ts
Normal file
144
packages/agent/tests/enhanced-read-file.test.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { createSystemTools } from '../src/system-tools.js';
|
||||
import type { ToolDefinition } from '../src/tools.js';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
|
||||
describe('enhanced read_file', () => {
|
||||
let workspace: string;
|
||||
let tools: ToolDefinition[];
|
||||
let readFile: ToolDefinition;
|
||||
|
||||
beforeEach(() => {
|
||||
workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-read-test-'));
|
||||
tools = createSystemTools(workspace);
|
||||
readFile = tools.find((t) => t.name === 'read_file')!;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
return;
|
||||
} catch {
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('default behavior (backward compatible)', () => {
|
||||
it('reads full file without options', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'line1\nline2\nline3');
|
||||
const result = await readFile.execute({ path: 'test.txt' });
|
||||
expect(result).toBe('line1\nline2\nline3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('offset', () => {
|
||||
it('reads from a specific offset (1-based)', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'line1\nline2\nline3\nline4\nline5');
|
||||
const result = await readFile.execute({ path: 'test.txt', offset: 3 });
|
||||
expect(result).toBe('line3\nline4\nline5');
|
||||
});
|
||||
|
||||
it('offset=1 returns full file', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'a\nb\nc');
|
||||
const result = await readFile.execute({ path: 'test.txt', offset: 1 });
|
||||
expect(result).toBe('a\nb\nc');
|
||||
});
|
||||
|
||||
it('offset beyond file length returns empty', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'a\nb');
|
||||
const result = await readFile.execute({ path: 'test.txt', offset: 100 });
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('limit', () => {
|
||||
it('limits number of lines returned', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'line1\nline2\nline3\nline4\nline5');
|
||||
const result = await readFile.execute({ path: 'test.txt', limit: 2 });
|
||||
expect(result).toBe('line1\nline2');
|
||||
});
|
||||
|
||||
it('limit larger than file returns full file', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'a\nb');
|
||||
const result = await readFile.execute({ path: 'test.txt', limit: 100 });
|
||||
expect(result).toBe('a\nb');
|
||||
});
|
||||
});
|
||||
|
||||
describe('offset + limit combined', () => {
|
||||
it('reads a window of lines', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'l1\nl2\nl3\nl4\nl5\nl6\nl7');
|
||||
const result = await readFile.execute({ path: 'test.txt', offset: 3, limit: 3 });
|
||||
expect(result).toBe('l3\nl4\nl5');
|
||||
});
|
||||
});
|
||||
|
||||
describe('line_numbers', () => {
|
||||
it('prefixes lines with line numbers when enabled', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'alpha\nbeta\ngamma');
|
||||
const result = await readFile.execute({ path: 'test.txt', line_numbers: true });
|
||||
expect(result).toContain('1\talpha');
|
||||
expect(result).toContain('2\tbeta');
|
||||
expect(result).toContain('3\tgamma');
|
||||
});
|
||||
|
||||
it('right-aligns line numbers for large files', async () => {
|
||||
const lines = Array.from({ length: 100 }, (_, i) => `line${i + 1}`);
|
||||
fs.writeFileSync(path.join(workspace, 'big.txt'), lines.join('\n'));
|
||||
const result = await readFile.execute({ path: 'big.txt', line_numbers: true, limit: 3 });
|
||||
// Lines 1-3, max line num visible is 3, so pad width = 1
|
||||
expect(result).toContain('1\tline1');
|
||||
});
|
||||
|
||||
it('line numbers respect offset', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'a\nb\nc\nd\ne');
|
||||
const result = await readFile.execute({ path: 'test.txt', offset: 3, limit: 2, line_numbers: true });
|
||||
expect(result).toContain('3\tc');
|
||||
expect(result).toContain('4\td');
|
||||
expect(result).not.toContain('1\t');
|
||||
expect(result).not.toContain('2\t');
|
||||
});
|
||||
|
||||
it('does not add line numbers by default', async () => {
|
||||
fs.writeFileSync(path.join(workspace, 'test.txt'), 'hello');
|
||||
const result = await readFile.execute({ path: 'test.txt' });
|
||||
expect(result).toBe('hello');
|
||||
expect(result).not.toContain('\t');
|
||||
});
|
||||
});
|
||||
|
||||
describe('image file detection', () => {
|
||||
const imageExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'];
|
||||
|
||||
for (const ext of imageExts) {
|
||||
it(`detects .${ext} as image file`, async () => {
|
||||
const fpath = path.join(workspace, `photo.${ext}`);
|
||||
fs.writeFileSync(fpath, Buffer.from([0x89, 0x50, 0x4e, 0x47])); // fake binary
|
||||
const result = await readFile.execute({ path: `photo.${ext}` });
|
||||
expect(result).toMatch(/\[Image file: photo\.\w+, \d+ bytes\]/);
|
||||
});
|
||||
}
|
||||
|
||||
it('returns file size for image files', async () => {
|
||||
const fpath = path.join(workspace, 'test.png');
|
||||
const buf = Buffer.alloc(1024);
|
||||
fs.writeFileSync(fpath, buf);
|
||||
const result = await readFile.execute({ path: 'test.png' });
|
||||
expect(result).toContain('1024 bytes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PDF file detection', () => {
|
||||
it('returns fallback message when pdf-parse is not installed', async () => {
|
||||
const fpath = path.join(workspace, 'doc.pdf');
|
||||
fs.writeFileSync(fpath, Buffer.from('%PDF-1.4 fake'));
|
||||
const result = await readFile.execute({ path: 'doc.pdf' });
|
||||
// pdf-parse is almost certainly not installed in test env
|
||||
expect(result).toMatch(/\[PDF file: doc\.pdf, \d+ bytes/);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user