Skip to main content
Glama

AutoDev Codebase MCP Server

by anrgct
RooIgnoreController.test.ts16.3 kB
// npx jest src/core/ignore/__tests__/RooIgnoreController.test.ts import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../RooIgnoreController" import * as vscode from "vscode" import * as path from "path" import * as fs from "fs/promises" import { fileExistsAtPath } from "../../../utils/fs" // Mock dependencies jest.mock("fs/promises") jest.mock("../../../utils/fs") // Mock vscode jest.mock("vscode", () => { const mockDisposable = { dispose: jest.fn() } const mockEventEmitter = { event: jest.fn(), fire: jest.fn(), } return { workspace: { createFileSystemWatcher: jest.fn(() => ({ onDidCreate: jest.fn(() => mockDisposable), onDidChange: jest.fn(() => mockDisposable), onDidDelete: jest.fn(() => mockDisposable), dispose: jest.fn(), })), }, RelativePattern: jest.fn().mockImplementation((base, pattern) => ({ base, pattern, })), EventEmitter: jest.fn().mockImplementation(() => mockEventEmitter), Disposable: { from: jest.fn(), }, } }) describe("RooIgnoreController", () => { const TEST_CWD = "/test/path" let controller: RooIgnoreController let mockFileExists: jest.MockedFunction<typeof fileExistsAtPath> let mockReadFile: jest.MockedFunction<typeof fs.readFile> let mockWatcher: any beforeEach(() => { // Reset mocks jest.clearAllMocks() // Setup mock file watcher mockWatcher = { onDidCreate: jest.fn().mockReturnValue({ dispose: jest.fn() }), onDidChange: jest.fn().mockReturnValue({ dispose: jest.fn() }), onDidDelete: jest.fn().mockReturnValue({ dispose: jest.fn() }), dispose: jest.fn(), } // @ts-expect-error - Mocking vscode.workspace.createFileSystemWatcher.mockReturnValue(mockWatcher) // Setup fs mocks mockFileExists = fileExistsAtPath as jest.MockedFunction<typeof fileExistsAtPath> mockReadFile = fs.readFile as jest.MockedFunction<typeof fs.readFile> // Create controller controller = new RooIgnoreController(TEST_CWD) }) describe("initialization", () => { /** * Tests the controller initialization when .rooignore exists */ it("should load .rooignore patterns on initialization when file exists", async () => { // Setup mocks to simulate existing .rooignore file mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets.json") // Initialize controller await controller.initialize() // Verify file was checked and read expect(mockFileExists).toHaveBeenCalledWith(path.join(TEST_CWD, ".rooignore")) expect(mockReadFile).toHaveBeenCalledWith(path.join(TEST_CWD, ".rooignore"), "utf8") // Verify content was stored expect(controller.rooIgnoreContent).toBe("node_modules\n.git\nsecrets.json") // Test that ignore patterns were applied expect(controller.validateAccess("node_modules/package.json")).toBe(false) expect(controller.validateAccess("src/app.ts")).toBe(true) expect(controller.validateAccess(".git/config")).toBe(false) expect(controller.validateAccess("secrets.json")).toBe(false) }) /** * Tests the controller behavior when .rooignore doesn't exist */ it("should allow all access when .rooignore doesn't exist", async () => { // Setup mocks to simulate missing .rooignore file mockFileExists.mockResolvedValue(false) // Initialize controller await controller.initialize() // Verify no content was stored expect(controller.rooIgnoreContent).toBeUndefined() // All files should be accessible expect(controller.validateAccess("node_modules/package.json")).toBe(true) expect(controller.validateAccess("secrets.json")).toBe(true) }) /** * Tests the file watcher setup */ it("should set up file watcher for .rooignore changes", async () => { // Check that watcher was created with correct pattern expect(vscode.workspace.createFileSystemWatcher).toHaveBeenCalledWith( expect.objectContaining({ base: TEST_CWD, pattern: ".rooignore", }), ) // Verify event handlers were registered expect(mockWatcher.onDidCreate).toHaveBeenCalled() expect(mockWatcher.onDidChange).toHaveBeenCalled() expect(mockWatcher.onDidDelete).toHaveBeenCalled() }) /** * Tests error handling during initialization */ it("should handle errors when loading .rooignore", async () => { // Setup mocks to simulate error mockFileExists.mockResolvedValue(true) mockReadFile.mockRejectedValue(new Error("Test file read error")) // Spy on console.error const consoleSpy = jest.spyOn(console, "error").mockImplementation() // Initialize controller - shouldn't throw await controller.initialize() // Verify error was logged expect(consoleSpy).toHaveBeenCalledWith("Unexpected error loading .rooignore:", expect.any(Error)) // Cleanup consoleSpy.mockRestore() }) }) describe("validateAccess", () => { beforeEach(async () => { // Setup .rooignore content mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets/**\n*.log") await controller.initialize() }) /** * Tests basic path validation */ it("should correctly validate file access based on ignore patterns", () => { // Test different path patterns expect(controller.validateAccess("node_modules/package.json")).toBe(false) expect(controller.validateAccess("node_modules")).toBe(false) expect(controller.validateAccess("src/node_modules/file.js")).toBe(false) expect(controller.validateAccess(".git/HEAD")).toBe(false) expect(controller.validateAccess("secrets/api-keys.json")).toBe(false) expect(controller.validateAccess("logs/app.log")).toBe(false) // These should be allowed expect(controller.validateAccess("src/app.ts")).toBe(true) expect(controller.validateAccess("package.json")).toBe(true) expect(controller.validateAccess("secret-file.json")).toBe(true) }) /** * Tests handling of absolute paths */ it("should handle absolute paths correctly", () => { // Test with absolute paths const absolutePath = path.join(TEST_CWD, "node_modules/package.json") expect(controller.validateAccess(absolutePath)).toBe(false) const allowedAbsolutePath = path.join(TEST_CWD, "src/app.ts") expect(controller.validateAccess(allowedAbsolutePath)).toBe(true) }) /** * Tests handling of paths outside cwd */ it("should allow access to paths outside cwd", () => { // Path traversal outside cwd expect(controller.validateAccess("../outside-project/file.txt")).toBe(true) // Completely different path expect(controller.validateAccess("/etc/hosts")).toBe(true) }) /** * Tests the default behavior when no .rooignore exists */ it("should allow all access when no .rooignore content", async () => { // Create a new controller with no .rooignore mockFileExists.mockResolvedValue(false) const emptyController = new RooIgnoreController(TEST_CWD) await emptyController.initialize() // All paths should be allowed expect(emptyController.validateAccess("node_modules/package.json")).toBe(true) expect(emptyController.validateAccess("secrets/api-keys.json")).toBe(true) expect(emptyController.validateAccess(".git/HEAD")).toBe(true) }) }) describe("validateCommand", () => { beforeEach(async () => { // Setup .rooignore content mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets/**\n*.log") await controller.initialize() }) /** * Tests validation of file reading commands */ it("should block file reading commands accessing ignored files", () => { // Cat command accessing ignored file expect(controller.validateCommand("cat node_modules/package.json")).toBe("node_modules/package.json") // Grep command accessing ignored file expect(controller.validateCommand("grep pattern .git/config")).toBe(".git/config") // Commands accessing allowed files should return undefined expect(controller.validateCommand("cat src/app.ts")).toBeUndefined() expect(controller.validateCommand("less README.md")).toBeUndefined() }) /** * Tests commands with various arguments and flags */ it("should handle command arguments and flags correctly", () => { // Command with flags expect(controller.validateCommand("cat -n node_modules/package.json")).toBe("node_modules/package.json") // Command with multiple files (only first ignored file is returned) expect(controller.validateCommand("grep pattern src/app.ts node_modules/index.js")).toBe( "node_modules/index.js", ) // Command with PowerShell parameter style expect(controller.validateCommand("Get-Content -Path secrets/api-keys.json")).toBe("secrets/api-keys.json") // Arguments with colons are skipped due to the implementation // Adjust test to match actual implementation which skips arguments with colons expect(controller.validateCommand("Select-String -Path secrets/api-keys.json -Pattern key")).toBe( "secrets/api-keys.json", ) }) /** * Tests validation of non-file-reading commands */ it("should allow non-file-reading commands", () => { // Commands that don't access files directly expect(controller.validateCommand("ls -la")).toBeUndefined() expect(controller.validateCommand("echo 'Hello'")).toBeUndefined() expect(controller.validateCommand("cd node_modules")).toBeUndefined() expect(controller.validateCommand("npm install")).toBeUndefined() }) /** * Tests behavior when no .rooignore exists */ it("should allow all commands when no .rooignore exists", async () => { // Create a new controller with no .rooignore mockFileExists.mockResolvedValue(false) const emptyController = new RooIgnoreController(TEST_CWD) await emptyController.initialize() // All commands should be allowed expect(emptyController.validateCommand("cat node_modules/package.json")).toBeUndefined() expect(emptyController.validateCommand("grep pattern .git/config")).toBeUndefined() }) }) describe("filterPaths", () => { beforeEach(async () => { // Setup .rooignore content mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets/**\n*.log") await controller.initialize() }) /** * Tests filtering an array of paths */ it("should filter out ignored paths from an array", () => { const paths = [ "src/app.ts", "node_modules/package.json", "README.md", ".git/HEAD", "secrets/keys.json", "build/app.js", "logs/error.log", ] const filtered = controller.filterPaths(paths) // Expected filtered result expect(filtered).toEqual(["src/app.ts", "README.md", "build/app.js"]) // Length should be reduced expect(filtered.length).toBe(3) }) /** * Tests error handling in filterPaths */ it("should handle errors in filterPaths and fail closed", () => { // Mock validateAccess to throw an error jest.spyOn(controller, "validateAccess").mockImplementation(() => { throw new Error("Test error") }) // Spy on console.error const consoleSpy = jest.spyOn(console, "error").mockImplementation() // Should return empty array on error (fail closed) const result = controller.filterPaths(["file1.txt", "file2.txt"]) expect(result).toEqual([]) // Verify error was logged expect(consoleSpy).toHaveBeenCalledWith("Error filtering paths:", expect.any(Error)) // Cleanup consoleSpy.mockRestore() }) /** * Tests empty array handling */ it("should handle empty arrays", () => { const result = controller.filterPaths([]) expect(result).toEqual([]) }) }) describe("getInstructions", () => { /** * Tests instructions generation with .rooignore */ it("should generate formatted instructions when .rooignore exists", async () => { // Setup .rooignore content mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules\n.git\nsecrets/**") await controller.initialize() const instructions = controller.getInstructions() // Verify instruction format expect(instructions).toContain("# .rooignore") expect(instructions).toContain(LOCK_TEXT_SYMBOL) expect(instructions).toContain("node_modules") expect(instructions).toContain(".git") expect(instructions).toContain("secrets/**") }) /** * Tests behavior when no .rooignore exists */ it("should return undefined when no .rooignore exists", async () => { // Setup no .rooignore mockFileExists.mockResolvedValue(false) await controller.initialize() const instructions = controller.getInstructions() expect(instructions).toBeUndefined() }) }) describe("dispose", () => { /** * Tests proper cleanup of resources */ it("should dispose all registered disposables", () => { // Create spy for dispose methods const disposeSpy = jest.fn() // Manually add disposables to test controller["disposables"] = [{ dispose: disposeSpy }, { dispose: disposeSpy }, { dispose: disposeSpy }] // Call dispose controller.dispose() // Verify all disposables were disposed expect(disposeSpy).toHaveBeenCalledTimes(3) // Verify disposables array was cleared expect(controller["disposables"]).toEqual([]) }) }) describe("file watcher", () => { /** * Tests behavior when .rooignore is created */ it("should reload .rooignore when file is created", async () => { // Setup initial state without .rooignore mockFileExists.mockResolvedValue(false) await controller.initialize() // Verify initial state expect(controller.rooIgnoreContent).toBeUndefined() expect(controller.validateAccess("node_modules/package.json")).toBe(true) // Setup for the test mockFileExists.mockResolvedValue(false) // Initially no file exists // Create and initialize controller with no .rooignore controller = new RooIgnoreController(TEST_CWD) await controller.initialize() // Initial state check expect(controller.rooIgnoreContent).toBeUndefined() // Now simulate file creation mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules") // Force reload of .rooignore content manually await controller.initialize() // Now verify content was updated expect(controller.rooIgnoreContent).toBe("node_modules") // Verify access validation changed expect(controller.validateAccess("node_modules/package.json")).toBe(false) }) /** * Tests behavior when .rooignore is changed */ it("should reload .rooignore when file is changed", async () => { // Setup initial state with .rooignore mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules") await controller.initialize() // Verify initial state expect(controller.validateAccess("node_modules/package.json")).toBe(false) expect(controller.validateAccess(".git/config")).toBe(true) // Simulate file change mockReadFile.mockResolvedValue("node_modules\n.git") // Instead of relying on the onChange handler, manually reload // This is because the mock watcher doesn't actually trigger the reload in tests await controller.initialize() // Verify content was updated expect(controller.rooIgnoreContent).toBe("node_modules\n.git") // Verify access validation changed expect(controller.validateAccess("node_modules/package.json")).toBe(false) expect(controller.validateAccess(".git/config")).toBe(false) }) /** * Tests behavior when .rooignore is deleted */ it("should reset when .rooignore is deleted", async () => { // Setup initial state with .rooignore mockFileExists.mockResolvedValue(true) mockReadFile.mockResolvedValue("node_modules") await controller.initialize() // Verify initial state expect(controller.validateAccess("node_modules/package.json")).toBe(false) // Simulate file deletion mockFileExists.mockResolvedValue(false) // Find and trigger the onDelete handler const onDeleteHandler = mockWatcher.onDidDelete.mock.calls[0][0] await onDeleteHandler() // Verify content was reset expect(controller.rooIgnoreContent).toBeUndefined() // Verify access validation changed expect(controller.validateAccess("node_modules/package.json")).toBe(true) }) }) })

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/anrgct/autodev-codebase'

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