Skip to main content
Glama
DiscoverCollectionsHandler.test.ts6.16 kB
import { McpError } from '@modelcontextprotocol/sdk/types.js'; import { describe, test, expect, beforeEach, vi } from 'vitest'; import type { IBrunoCLI } from '../../../interfaces.js'; import * as security from '../../../security.js'; import { DiscoverCollectionsHandler } from '../../../tools/handlers/DiscoverCollectionsHandler.js'; vi.mock('../../../security.js', async () => { const actual = await vi.importActual('../../../security.js'); return { ...actual, validateToolParameters: vi.fn(), maskSecretsInError: vi.fn((error: Error) => error.message) }; }); describe('DiscoverCollectionsHandler', () => { let handler: DiscoverCollectionsHandler; let mockBrunoCLI: IBrunoCLI; const mockCollections = [ '/home/user/projects/api-tests', '/home/user/projects/integration-tests', '/home/user/workspace/my-collection' ]; beforeEach(() => { vi.clearAllMocks(); mockBrunoCLI = { isAvailable: vi.fn(), listRequests: vi.fn(), listEnvironments: vi.fn(), runRequest: vi.fn(), runCollection: vi.fn(), getRequestDetails: vi.fn(), validateCollection: vi.fn(), validateEnvironment: vi.fn(), discoverCollections: vi.fn().mockResolvedValue(mockCollections) }; handler = new DiscoverCollectionsHandler(mockBrunoCLI); vi.mocked(security.validateToolParameters).mockResolvedValue({ valid: true, errors: [] }); }); test('should return correct tool name', () => { expect(handler.getName()).toBe('bruno_discover_collections'); }); test('should discover collections successfully', async () => { const result = await handler.handle({ searchPath: '/home/user/projects' }); expect(security.validateToolParameters).toHaveBeenCalledWith({ collectionPath: '/home/user/projects' }); expect(mockBrunoCLI.discoverCollections).toHaveBeenCalledWith('/home/user/projects', 5); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); expect((result.content[0] as any).text).toContain('Found 3 Bruno collection(s)'); }); test('should list all discovered collections', async () => { const result = await handler.handle({ searchPath: '/home/user/projects' }); const output = (result.content[0] as any).text; expect(output).toContain('/home/user/projects/api-tests'); expect(output).toContain('/home/user/projects/integration-tests'); expect(output).toContain('/home/user/workspace/my-collection'); }); test('should use default maxDepth of 5 when not specified', async () => { await handler.handle({ searchPath: '/home/user/projects' }); expect(mockBrunoCLI.discoverCollections).toHaveBeenCalledWith( '/home/user/projects', 5 ); }); test('should use custom maxDepth when specified', async () => { await handler.handle({ searchPath: '/home/user/projects', maxDepth: 10 }); expect(mockBrunoCLI.discoverCollections).toHaveBeenCalledWith( '/home/user/projects', 10 ); }); test('should handle empty results gracefully', async () => { mockBrunoCLI.discoverCollections = vi.fn().mockResolvedValue([]); const result = await handler.handle({ searchPath: '/empty/directory' }); const output = (result.content[0] as any).text; expect(output).toContain('No Bruno collections found'); expect(output).toContain('A Bruno collection is a directory containing a bruno.json file'); }); test('should number collections in output', async () => { const result = await handler.handle({ searchPath: '/home/user/projects' }); const output = (result.content[0] as any).text; expect(output).toContain('1. /home/user/projects/api-tests'); expect(output).toContain('2. /home/user/projects/integration-tests'); expect(output).toContain('3. /home/user/workspace/my-collection'); }); test('should throw McpError when validation fails', async () => { vi.mocked(security.validateToolParameters).mockResolvedValue({ valid: false, errors: ['Invalid search path'] }); await expect( handler.handle({ searchPath: '/invalid/path' }) ).rejects.toThrow(McpError); await expect( handler.handle({ searchPath: '/invalid/path' }) ).rejects.toThrow('Invalid search path'); }); test('should throw McpError on Bruno CLI failure', async () => { mockBrunoCLI.discoverCollections = vi.fn().mockRejectedValue( new Error('Permission denied') ); await expect( handler.handle({ searchPath: '/valid/path' }) ).rejects.toThrow(McpError); await expect( handler.handle({ searchPath: '/valid/path' }) ).rejects.toThrow('Failed to discover collections'); }); test('should call maskSecretsInError on errors', async () => { const error = new Error('Error with SECRET=abc123'); mockBrunoCLI.discoverCollections = vi.fn().mockRejectedValue(error); vi.mocked(security.maskSecretsInError).mockReturnValue('Error with SECRET=***'); await expect( handler.handle({ searchPath: '/valid/path' }) ).rejects.toThrow(); expect(security.maskSecretsInError).toHaveBeenCalledWith(error); }); test('should throw error for missing searchPath parameter', async () => { await expect(handler.handle({})).rejects.toThrow(); }); test('should throw error for invalid parameter types', async () => { await expect(handler.handle({ searchPath: 123 })).rejects.toThrow(); await expect(handler.handle({ searchPath: null })).rejects.toThrow(); await expect( handler.handle({ searchPath: '/valid/path', maxDepth: 'invalid' }) ).rejects.toThrow(); }); test('should handle single collection found', async () => { mockBrunoCLI.discoverCollections = vi.fn().mockResolvedValue(['/home/user/single-collection']); const result = await handler.handle({ searchPath: '/home/user' }); const output = (result.content[0] as any).text; expect(output).toContain('Found 1 Bruno collection(s)'); expect(output).toContain('1. /home/user/single-collection'); }); });

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/jcr82/bruno-mcp-server'

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