Skip to main content
Glama

vulcan-file-ops

grep.test.ts12.7 kB
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; import fs from "fs/promises"; import path from "path"; import os from "os"; import { grepFilesWithValidation } from "../utils/lib.js"; import { setAllowedDirectories, setIgnoredFolders } from "../utils/lib.js"; describe("Grep Tool", () => { let testDir: string; beforeEach(async () => { // Create temporary test directory testDir = path.join(os.tmpdir(), `grep-test-${Date.now()}`); await fs.mkdir(testDir, { recursive: true }); setAllowedDirectories([testDir]); setIgnoredFolders([]); }); afterEach(async () => { // Cleanup try { await fs.rm(testDir, { recursive: true, force: true }); } catch {} }); describe("Basic Text Search", () => { it("should find matches in a single file", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Hello World\nTest Line\nHello Again"); const result = await grepFilesWithValidation("Hello", testDir, [testDir]); expect(result.totalMatches).toBe(2); expect(result.matches).toHaveLength(2); expect(result.matches![0].line).toBe(1); expect(result.matches![1].line).toBe(3); }); it("should handle regex patterns", async () => { const testFile = path.join(testDir, "code.js"); await fs.writeFile( testFile, "function test() {}\n" + "const myVar = 123;\n" + "function anotherTest() {}" ); const result = await grepFilesWithValidation( "function\\s+\\w+", testDir, [testDir] ); expect(result.totalMatches).toBe(2); expect(result.matches).toHaveLength(2); }); it("should respect case insensitive flag", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Hello\nhello\nHELLO"); const result = await grepFilesWithValidation( "hello", testDir, [testDir], { caseInsensitive: true, } ); expect(result.totalMatches).toBe(3); }); it("should respect case sensitive by default", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Hello\nhello\nHELLO"); const result = await grepFilesWithValidation( "hello", testDir, [testDir], { caseInsensitive: false, } ); expect(result.totalMatches).toBe(1); expect(result.matches![0].content).toBe("hello"); }); }); describe("Context Lines", () => { it("should include context before matches", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Line 1\nLine 2\nMATCH\nLine 4"); const result = await grepFilesWithValidation( "MATCH", testDir, [testDir], { contextBefore: 2, } ); expect(result.matches![0].contextBefore).toEqual(["Line 1", "Line 2"]); }); it("should include context after matches", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Line 1\nMATCH\nLine 3\nLine 4"); const result = await grepFilesWithValidation( "MATCH", testDir, [testDir], { contextAfter: 2, } ); expect(result.matches![0].contextAfter).toEqual(["Line 3", "Line 4"]); }); it("should include context before and after matches", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Line 1\nLine 2\nMATCH\nLine 4\nLine 5"); const result = await grepFilesWithValidation( "MATCH", testDir, [testDir], { contextBefore: 1, contextAfter: 1, } ); expect(result.matches![0].contextBefore).toEqual(["Line 2"]); expect(result.matches![0].contextAfter).toEqual(["Line 4"]); }); }); describe("Output Modes", () => { beforeEach(async () => { await fs.writeFile( path.join(testDir, "file1.txt"), "Match here\nNo match" ); await fs.writeFile( path.join(testDir, "file2.txt"), "Match again\nMatch twice" ); await fs.writeFile(path.join(testDir, "file3.txt"), "No match at all"); }); it("should support content output mode (default)", async () => { const result = await grepFilesWithValidation( "Match", testDir, [testDir], { outputMode: "content", } ); expect(result.mode).toBe("content"); expect(result.totalMatches).toBe(3); expect(result.matches).toBeDefined(); expect(result.matches!.length).toBeGreaterThan(0); }); it("should support files_with_matches output mode", async () => { const result = await grepFilesWithValidation( "Match", testDir, [testDir], { outputMode: "files_with_matches", } ); expect(result.mode).toBe("files_with_matches"); expect(result.files).toHaveLength(2); expect(result.files).toContain(path.join(testDir, "file1.txt")); expect(result.files).toContain(path.join(testDir, "file2.txt")); }); it("should support count output mode", async () => { const result = await grepFilesWithValidation( "Match", testDir, [testDir], { outputMode: "count", } ); expect(result.mode).toBe("count"); expect(result.counts!.size).toBe(2); expect(result.counts!.get(path.join(testDir, "file1.txt"))).toBe(1); expect(result.counts!.get(path.join(testDir, "file2.txt"))).toBe(2); }); }); describe("File Type Filtering", () => { beforeEach(async () => { await fs.writeFile(path.join(testDir, "test.js"), "console.log('test');"); await fs.writeFile(path.join(testDir, "test.py"), "print('test')"); await fs.writeFile(path.join(testDir, "test.txt"), "test"); }); it("should filter by file type (js)", async () => { const result = await grepFilesWithValidation("test", testDir, [testDir], { fileType: "js", }); expect(result.filesSearched).toBe(1); }); it("should filter by file type (py)", async () => { const result = await grepFilesWithValidation("test", testDir, [testDir], { fileType: "py", }); expect(result.filesSearched).toBe(1); }); }); describe("Glob Pattern Filtering", () => { beforeEach(async () => { await fs.mkdir(path.join(testDir, "src"), { recursive: true }); await fs.writeFile(path.join(testDir, "src", "app.js"), "test"); await fs.writeFile(path.join(testDir, "README.md"), "test"); }); it("should filter by glob pattern", async () => { const result = await grepFilesWithValidation("test", testDir, [testDir], { globPattern: "*.js", }); expect(result.filesSearched).toBe(0); // *.js only matches current dir }); it("should support recursive glob patterns", async () => { const result = await grepFilesWithValidation("test", testDir, [testDir], { globPattern: "**/*.js", }); expect(result.filesSearched).toBe(1); }); }); describe("Head Limit", () => { beforeEach(async () => { await fs.writeFile( path.join(testDir, "test.txt"), Array(100).fill("MATCH").join("\n") ); }); it("should limit results with head_limit in content mode", async () => { const result = await grepFilesWithValidation( "MATCH", testDir, [testDir], { outputMode: "content", headLimit: 10, } ); expect(result.matches!.length).toBe(10); expect(result.totalMatches).toBeGreaterThanOrEqual(10); }); }); describe("Multiline Mode", () => { it("should support multiline patterns", async () => { const testFile = path.join(testDir, "test.txt"); await fs.writeFile(testFile, "Start block\nMiddle\nEnd block"); // Multiline mode allows . to match newlines const result = await grepFilesWithValidation( "Start", testDir, [testDir], { multiline: true, } ); expect(result.totalMatches).toBeGreaterThan(0); expect(result.matches![0].content).toContain("Start"); }); }); describe("Security & Performance", () => { it("should respect allowed directories", async () => { const outsideDir = path.join(os.tmpdir(), `outside-${Date.now()}`); await fs.mkdir(outsideDir, { recursive: true }); await fs.writeFile(path.join(outsideDir, "test.txt"), "MATCH"); try { // Should not search outside allowed directories const result = await grepFilesWithValidation("MATCH", outsideDir, [ testDir, ]); // If the path validation works correctly, this should throw or return 0 results expect(result.totalMatches).toBe(0); } catch (error) { // Expected - path validation should reject expect(error).toBeDefined(); } finally { await fs.rm(outsideDir, { recursive: true, force: true }); } }); it("should respect ignored folders", async () => { setIgnoredFolders(["node_modules"]); await fs.mkdir(path.join(testDir, "node_modules"), { recursive: true }); await fs.writeFile( path.join(testDir, "node_modules", "test.js"), "MATCH" ); await fs.writeFile(path.join(testDir, "app.js"), "MATCH"); const result = await grepFilesWithValidation("MATCH", testDir, [testDir]); expect(result.filesSearched).toBe(1); expect(result.totalMatches).toBe(1); }); it("should handle invalid regex gracefully", async () => { await expect( grepFilesWithValidation("[invalid(regex", testDir, [testDir]) ).rejects.toThrow(/Invalid regex pattern/); }); it("should skip binary files gracefully", async () => { // Create a binary file (will fail UTF-8 decode) const binaryFile = path.join(testDir, "binary.bin"); await fs.writeFile(binaryFile, Buffer.from([0xff, 0xfe, 0xfd])); await fs.writeFile(path.join(testDir, "text.txt"), "MATCH"); const result = await grepFilesWithValidation("MATCH", testDir, [testDir]); // Should skip binary file and only find match in text file expect(result.totalMatches).toBe(1); }); }); describe("Recursive Directory Search", () => { beforeEach(async () => { await fs.mkdir(path.join(testDir, "sub1", "sub2"), { recursive: true }); await fs.writeFile(path.join(testDir, "file1.txt"), "MATCH"); await fs.writeFile(path.join(testDir, "sub1", "file2.txt"), "MATCH"); await fs.writeFile( path.join(testDir, "sub1", "sub2", "file3.txt"), "MATCH" ); }); it("should search recursively through directories", async () => { const result = await grepFilesWithValidation("MATCH", testDir, [testDir]); expect(result.totalMatches).toBe(3); expect(result.filesSearched).toBe(3); }); it("should limit recursion depth", async () => { // Create very deep directory structure let deepPath = testDir; for (let i = 0; i < 15; i++) { deepPath = path.join(deepPath, `level${i}`); await fs.mkdir(deepPath, { recursive: true }); } await fs.writeFile(path.join(deepPath, "deep.txt"), "MATCH"); const result = await grepFilesWithValidation("MATCH", testDir, [testDir]); // Should not reach the very deep file due to depth limit (max depth: 10) expect(result.filesSearched).toBeLessThan(10); }); }); describe("Single File Search", () => { it("should search a single file when given a file path", async () => { const testFile = path.join(testDir, "single.txt"); await fs.writeFile(testFile, "MATCH\nNO\nMATCH"); const result = await grepFilesWithValidation("MATCH", testFile, [ testDir, ]); expect(result.totalMatches).toBe(2); expect(result.filesSearched).toBe(1); }); }); describe("Empty Results", () => { it("should return empty results when no matches found", async () => { await fs.writeFile(path.join(testDir, "test.txt"), "No matches here"); const result = await grepFilesWithValidation("NOMATCH", testDir, [ testDir, ]); expect(result.totalMatches).toBe(0); expect(result.matches).toHaveLength(0); expect(result.filesSearched).toBe(1); }); }); });

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