Skip to main content
Glama
AgentManager.test.ts11.9 kB
import { AgentManager } from 'src/agents/AgentManager' import type { ServerConfig } from 'src/config/ServerConfig' import type { AgentDefinition } from 'src/types/AgentDefinition' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' // Mock fs module vi.mock('node:fs', () => ({ default: { promises: { readdir: vi.fn(), readFile: vi.fn(), stat: vi.fn(), }, }, })) // Mock path module vi.mock('node:path', () => ({ default: { resolve: vi.fn(), join: vi.fn(), basename: vi.fn(), }, resolve: vi.fn(), join: vi.fn(), basename: vi.fn(), })) // Import mocked modules import fs from 'node:fs' import path from 'node:path' // Type the mocked functions const mockReaddir = vi.mocked(fs.promises.readdir) const mockReadFile = vi.mocked(fs.promises.readFile) const mockStat = vi.mocked(fs.promises.stat) const mockResolve = vi.mocked(path.resolve) const mockJoin = vi.mocked(path.join) const mockBasename = vi.mocked(path.basename) describe('AgentManager', () => { let agentManager: AgentManager let mockConfig: ServerConfig beforeEach(() => { // Clear all mock functions mockReaddir.mockClear() mockReadFile.mockClear() mockStat.mockClear() mockResolve.mockClear() mockJoin.mockClear() mockBasename.mockClear() // Create mock config mockConfig = { agentsDir: '/test/agents', serverName: 'test-server', serverVersion: '1.0.0', agentType: 'cursor', logLevel: 'info', executionTimeoutMs: 300000, } as ServerConfig agentManager = new AgentManager(mockConfig) }) afterEach(() => { // Clear all mock functions mockReaddir.mockClear() mockReadFile.mockClear() mockStat.mockClear() mockResolve.mockClear() mockJoin.mockClear() mockBasename.mockClear() }) describe('File Discovery', () => { it('should discover .md and .txt files in agents directory', async () => { // Arrange const mockFiles = ['agent1.md', 'agent2.txt', 'readme.pdf', 'config.json'] const mockStats = { mtime: new Date('2025-01-01') } const mockContent = '# Test Agent\nThis is a test agent.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockStat.mockResolvedValue(mockStats as fs.Stats) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockImplementation((dir, file) => `${dir}/${file}`) mockBasename.mockImplementation((filePath) => { const parts = filePath.split('/') return parts[parts.length - 1] }) // Act const agents = await agentManager.listAgents() // Assert expect(agents).toHaveLength(2) // Only .md and .txt files expect(mockReaddir).toHaveBeenCalledWith('/test/agents') expect(agents.some((agent) => agent.name === 'agent1')).toBe(true) expect(agents.some((agent) => agent.name === 'agent2')).toBe(true) }) it('should handle empty agents directory', async () => { // Arrange mockReaddir.mockResolvedValue([] as unknown as fs.Dirent[]) mockResolve.mockReturnValue('/test/agents') // Act const agents = await agentManager.listAgents() // Assert expect(agents).toHaveLength(0) expect(mockReaddir).toHaveBeenCalledWith('/test/agents') }) it('should handle directory read errors', async () => { // Arrange mockReaddir.mockRejectedValue(new Error('Directory not found')) mockResolve.mockReturnValue('/test/agents') // Act & Assert await expect(agentManager.listAgents()).rejects.toThrow( 'Failed to load agents from directory: /test/agents' ) }) }) describe('Agent Definition Parsing', () => { it('should parse agent definition from markdown file content', async () => { // Arrange const mockFiles = ['test-agent.md'] const mockStats = { mtime: new Date('2025-01-01') } const mockContent = '# Test Agent\nThis is a comprehensive test agent for validation.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockStat.mockResolvedValue(mockStats as fs.Stats) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockReturnValue('/test/agents/test-agent.md') mockBasename.mockReturnValue('test-agent.md') // Act const agent = await agentManager.getAgent('test-agent') // Assert expect(agent).toBeDefined() expect(agent!.name).toBe('test-agent') expect(agent!.description).toBe('Test Agent') expect(agent!.content).toBe(mockContent) expect(agent!.filePath).toBe('/test/agents/test-agent.md') expect(agent!.lastModified).toEqual(mockStats.mtime) }) it('should extract description from first heading in markdown', async () => { // Arrange const mockFiles = ['agent.md'] const mockStats = { mtime: new Date('2025-01-01') } const mockContent = `Some preamble text # My Custom Agent This agent does amazing things.` mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockStat.mockResolvedValue(mockStats as fs.Stats) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockReturnValue('/test/agents/agent.md') mockBasename.mockReturnValue('agent.md') // Act const agent = await agentManager.getAgent('agent') // Assert expect(agent).toBeDefined() expect(agent!.description).toBe('My Custom Agent') }) it('should fallback to first line if no heading found', async () => { // Arrange const mockFiles = ['simple-agent.txt'] const mockStats = { mtime: new Date('2025-01-01') } const mockContent = 'Simple agent for basic tasks\nWith some additional content.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockStat.mockResolvedValue(mockStats as fs.Stats) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockReturnValue('/test/agents/simple-agent.txt') mockBasename.mockReturnValue('simple-agent.txt') // Act const agent = await agentManager.getAgent('simple-agent') // Assert expect(agent).toBeDefined() expect(agent!.description).toBe('Simple agent for basic tasks') }) it('should handle file read errors gracefully', async () => { // Arrange const mockFiles = ['broken-agent.md'] mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockReadFile.mockRejectedValue(new Error('Permission denied')) mockResolve.mockReturnValue('/test/agents') mockJoin.mockReturnValue('/test/agents/broken-agent.md') // Act const agents = await agentManager.listAgents() // Assert expect(agents).toHaveLength(0) // Should skip broken files }) }) describe('Agent Loading', () => { it('should return consistent agent data across multiple requests', async () => { // Arrange const mockFiles = ['cached-agent.md'] const mockContent = '# Cached Agent\nThis agent should be loaded.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockReturnValue('/test/agents/cached-agent.md') mockBasename.mockReturnValue('cached-agent.md') // Act const firstCall = await agentManager.getAgent('cached-agent') const secondCall = await agentManager.getAgent('cached-agent') // Assert - focus on behavior: same agent data is returned expect(firstCall).toBeDefined() expect(secondCall).toBeDefined() expect(firstCall!.name).toBe(secondCall!.name) expect(firstCall!.content).toBe(secondCall!.content) }) it('should return all agents from directory', async () => { // Arrange const mockFiles = ['agent1.md', 'agent2.txt'] const mockContent = '# Test Agent\nTest content.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockImplementation((dir, file) => `${dir}/${file}`) mockBasename.mockImplementation((filePath) => { const parts = filePath.split('/') return parts[parts.length - 1] }) // Act const agents = await agentManager.listAgents() // Assert - focus on behavior: correct number and names of agents expect(agents).toHaveLength(2) expect(agents.map((a) => a.name)).toContain('agent1') expect(agents.map((a) => a.name)).toContain('agent2') }) }) describe('Agent Refresh', () => { it('should detect newly added agents after refresh', async () => { // Arrange const initialFiles = ['initial-agent.md'] const refreshedFiles = ['initial-agent.md', 'new-agent.md'] const mockContent = '# Test Agent\nTest content.' mockReadFile.mockResolvedValue(mockContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockImplementation((dir, file) => `${dir}/${file}`) mockBasename.mockImplementation((filePath) => { const parts = filePath.split('/') return parts[parts.length - 1] }) // Set up sequential mock responses mockReaddir .mockResolvedValueOnce(initialFiles as unknown as fs.Dirent[]) // Initial listAgents .mockResolvedValueOnce(refreshedFiles as unknown as fs.Dirent[]) // refreshAgents .mockResolvedValueOnce(refreshedFiles as unknown as fs.Dirent[]) // Final listAgents // Act - Initial load const initialAgents = await agentManager.listAgents() // Act - Refresh (simulates new file added to directory) await agentManager.refreshAgents() const refreshedAgents = await agentManager.listAgents() // Assert - focus on behavior: new agent is now visible expect(initialAgents).toHaveLength(1) expect(initialAgents.map((a) => a.name)).toContain('initial-agent') expect(refreshedAgents).toHaveLength(2) expect(refreshedAgents.map((a) => a.name)).toContain('initial-agent') expect(refreshedAgents.map((a) => a.name)).toContain('new-agent') }) }) describe('Agent Retrieval', () => { it('should return undefined for non-existent agent', async () => { // Arrange mockReaddir.mockResolvedValue([] as unknown as fs.Dirent[]) mockResolve.mockReturnValue('/test/agents') // Act const agent = await agentManager.getAgent('non-existent') // Assert expect(agent).toBeUndefined() }) it('should return correct agent by name', async () => { // Arrange const mockFiles = ['target-agent.md', 'other-agent.txt'] const mockStats = { mtime: new Date('2025-01-01') } const targetContent = '# Target Agent\nThis is the target agent.' const otherContent = '# Other Agent\nThis is the other agent.' mockReaddir.mockResolvedValue(mockFiles as unknown as fs.Dirent[]) mockStat.mockResolvedValue(mockStats as fs.Stats) mockReadFile.mockResolvedValueOnce(targetContent).mockResolvedValueOnce(otherContent) mockResolve.mockReturnValue('/test/agents') mockJoin.mockImplementation((dir, file) => `${dir}/${file}`) mockBasename.mockImplementation((filePath) => { const parts = filePath.split('/') return parts[parts.length - 1] }) // Act const agent = await agentManager.getAgent('target-agent') // Assert expect(agent).toBeDefined() expect(agent!.name).toBe('target-agent') expect(agent!.description).toBe('Target Agent') expect(agent!.content).toBe(targetContent) }) }) })

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/shinpr/sub-agents-mcp'

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