Skip to main content
Glama
read-tools-range.test.ts8.57 kB
import { describe, test, expect, beforeAll, afterAll } from "@jest/globals"; import { rangeFile } from "../utils/lib.js"; import { handleReadTool } from "../tools/read-tools.js"; import { setAllowedDirectories } from "../utils/lib.js"; import fs from "fs/promises"; import path from "path"; import os from "os"; const TEST_WORKSPACE = path.join(os.tmpdir(), "test-range-file"); describe("rangeFile utility function", () => { let testFilePath: string; beforeAll(async () => { // Create test workspace await fs.mkdir(TEST_WORKSPACE, { recursive: true }); setAllowedDirectories([TEST_WORKSPACE]); }); afterAll(async () => { // Cleanup test workspace await fs.rm(TEST_WORKSPACE, { recursive: true, force: true }); }); beforeEach(async () => { // Create test file with known content testFilePath = path.join(TEST_WORKSPACE, "test-range-file.txt"); const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`); await fs.writeFile(testFilePath, lines.join("\n")); }); afterEach(async () => { try { await fs.unlink(testFilePath); } catch { // File may not exist } }); test("should read middle range of lines", async () => { const result = await rangeFile(testFilePath, 10, 20); const lines = result.split("\n"); expect(lines).toHaveLength(11); // 10 to 20 inclusive expect(lines[0]).toBe("Line 10"); expect(lines[10]).toBe("Line 20"); }); test("should read single line", async () => { const result = await rangeFile(testFilePath, 42, 42); expect(result).toBe("Line 42"); }); test("should read first few lines (equivalent to head)", async () => { const result = await rangeFile(testFilePath, 1, 5); const lines = result.split("\n"); expect(lines).toHaveLength(5); expect(lines[0]).toBe("Line 1"); expect(lines[4]).toBe("Line 5"); }); test("should read last few lines (similar to tail)", async () => { const result = await rangeFile(testFilePath, 96, 100); const lines = result.split("\n"); expect(lines).toHaveLength(5); expect(lines[0]).toBe("Line 96"); expect(lines[4]).toBe("Line 100"); }); test("should handle range extending beyond file end", async () => { const result = await rangeFile(testFilePath, 95, 200); const lines = result.split("\n"); expect(lines.length).toBeLessThanOrEqual(6); // Only 95-100 exist expect(lines[0]).toBe("Line 95"); }); test("should handle empty result when startLine > file length", async () => { const result = await rangeFile(testFilePath, 200, 300); expect(result).toBe(""); }); test("should handle large ranges efficiently", async () => { // Create larger test file const largeFilePath = path.join(TEST_WORKSPACE, "large-test.txt"); const largeLines = Array.from({ length: 10000 }, (_, i) => `Line ${i + 1}`); await fs.writeFile(largeFilePath, largeLines.join("\n")); const result = await rangeFile(largeFilePath, 5000, 6000); const lines = result.split("\n"); expect(lines).toHaveLength(1001); // 5000 to 6000 inclusive expect(lines[0]).toBe("Line 5000"); expect(lines[1000]).toBe("Line 6000"); await fs.unlink(largeFilePath); }); test("should handle files without trailing newline", async () => { const noNewlinePath = path.join(TEST_WORKSPACE, "no-newline.txt"); await fs.writeFile(noNewlinePath, "Line 1\nLine 2\nLine 3"); const result = await rangeFile(noNewlinePath, 2, 3); const lines = result.split("\n"); expect(lines).toHaveLength(2); expect(lines[0]).toBe("Line 2"); expect(lines[1]).toBe("Line 3"); await fs.unlink(noNewlinePath); }); test("should handle CRLF line endings", async () => { const crlfPath = path.join(TEST_WORKSPACE, "crlf.txt"); await fs.writeFile(crlfPath, "Line 1\r\nLine 2\r\nLine 3\r\n"); const result = await rangeFile(crlfPath, 1, 2); const lines = result.split("\n"); expect(lines).toHaveLength(2); // CRLF should be handled - \r remains with the line expect(lines[0]).toBe("Line 1\r"); expect(lines[1]).toBe("Line 2\r"); await fs.unlink(crlfPath); }); test("should handle empty file", async () => { const emptyPath = path.join(TEST_WORKSPACE, "empty.txt"); await fs.writeFile(emptyPath, ""); const result = await rangeFile(emptyPath, 1, 10); expect(result).toBe(""); await fs.unlink(emptyPath); }); test("should handle very long lines (>1KB)", async () => { const longLinePath = path.join(TEST_WORKSPACE, "long-lines.txt"); const longLine1 = "A".repeat(2000); const longLine2 = "B".repeat(2000); const longLine3 = "C".repeat(2000); await fs.writeFile( longLinePath, `${longLine1}\n${longLine2}\n${longLine3}` ); const result = await rangeFile(longLinePath, 2, 2); expect(result).toBe(longLine2); await fs.unlink(longLinePath); }); }); describe("read_file with range mode", () => { let testFilePath: string; beforeAll(async () => { await fs.mkdir(TEST_WORKSPACE, { recursive: true }); setAllowedDirectories([TEST_WORKSPACE]); // Create test file testFilePath = path.join(TEST_WORKSPACE, "test-integration.txt"); const lines = Array.from({ length: 100 }, (_, i) => `Line ${i + 1}`); await fs.writeFile(testFilePath, lines.join("\n")); }); afterAll(async () => { await fs.rm(TEST_WORKSPACE, { recursive: true, force: true }); }); test("should validate range parameters correctly", async () => { // Valid range request const result = await handleReadTool("read_file", { path: testFilePath, mode: "range", startLine: 10, endLine: 20, }); expect(result.content).toBeDefined(); expect(result.content[0].type).toBe("text"); const text = (result.content[0] as { type: string; text: string }).text; expect(text).toContain("Line 10"); expect(text).toContain("Line 20"); }); test("should reject range without startLine", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "range", endLine: 20, }) ).rejects.toThrow(); }); test("should reject range without endLine", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "range", startLine: 10, }) ).rejects.toThrow(); }); test("should reject range where startLine > endLine", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "range", startLine: 20, endLine: 10, }) ).rejects.toThrow(); }); test("should reject range with lines parameter", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "range", startLine: 10, endLine: 20, lines: 5, // Should not be provided in range mode }) ).rejects.toThrow(); }); test("should reject head mode with startLine/endLine", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "head", lines: 10, startLine: 1, }) ).rejects.toThrow(); }); test("should reject full mode with any parameters", async () => { await expect( handleReadTool("read_file", { path: testFilePath, mode: "full", lines: 10, }) ).rejects.toThrow(); }); test("should work with existing modes (head, tail, full)", async () => { // Test head mode still works const headResult = await handleReadTool("read_file", { path: testFilePath, mode: "head", lines: 5, }); const headText = (headResult.content[0] as { type: string; text: string }) .text; expect(headText).toContain("Line 1"); expect(headText).toContain("Line 5"); // Test tail mode still works const tailResult = await handleReadTool("read_file", { path: testFilePath, mode: "tail", lines: 5, }); const tailText = (tailResult.content[0] as { type: string; text: string }) .text; expect(tailText).toContain("Line 96"); expect(tailText).toContain("Line 100"); // Test full mode still works const fullResult = await handleReadTool("read_file", { path: testFilePath, mode: "full", }); const fullText = (fullResult.content[0] as { type: string; text: string }) .text; expect(fullText).toContain("Line 1"); expect(fullText).toContain("Line 100"); }); });

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/n0zer0d4y/vulcan-file-ops'

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