list-agents.test.js•11.1 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { handleListAgents, listAgentsToolDefinition } from '../../../tools/agents/list-agents.js';
import { createMockLettaServer } from '../../utils/mock-server.js';
import { fixtures } from '../../utils/test-fixtures.js';
import { expectValidToolResponse } from '../../utils/test-helpers.js';
describe('List Agents', () => {
    let mockServer;
    beforeEach(() => {
        mockServer = createMockLettaServer();
    });
    afterEach(() => {
        vi.restoreAllMocks();
    });
    describe('Tool Definition', () => {
        it('should have correct tool definition', () => {
            expect(listAgentsToolDefinition.name).toBe('list_agents');
            expect(listAgentsToolDefinition.description).toContain('List all available agents');
            expect(listAgentsToolDefinition.inputSchema.required).toEqual([]);
            expect(listAgentsToolDefinition.inputSchema.properties).toHaveProperty('filter');
        });
        it('should have optional filter parameter', () => {
            const filterProp = listAgentsToolDefinition.inputSchema.properties.filter;
            expect(filterProp.type).toBe('string');
            expect(filterProp.description).toContain('Optional filter');
        });
    });
    describe('Functionality Tests', () => {
        it('should list all agents without filter', async () => {
            const agents = [
                fixtures.agent.basic,
                fixtures.agent.minimal,
                { id: 'agent-789', name: 'Third Agent', description: 'Another test agent' },
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, {});
            // Verify API call
            expect(mockServer.api.get).toHaveBeenCalledWith(
                '/agents/',
                expect.objectContaining({ headers: expect.any(Object) }),
            );
            // Verify response
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(3);
            expect(data.agents).toHaveLength(3);
            // Verify summarized agent structure
            data.agents.forEach((agent, index) => {
                expect(agent).toHaveProperty('id');
                expect(agent).toHaveProperty('name');
                // The description property may or may not exist in the response
                // depending on whether the source agent had it
                if (index === 1) {
                    // The minimal agent fixture might not have description property
                    // in the response even though tool maps it
                    expect(Object.keys(agent).sort()).toEqual(['id', 'name']);
                } else {
                    expect(agent).toHaveProperty('description');
                    expect(agent.description).toBe(agents[index].description);
                    expect(Object.keys(agent).sort()).toEqual(['description', 'id', 'name']);
                }
                // Verify values match
                expect(agent.id).toBe(agents[index].id);
                expect(agent.name).toBe(agents[index].name);
            });
        });
        it('should handle empty agent list', async () => {
            mockServer.api.get.mockResolvedValueOnce({ data: [] });
            const result = await handleListAgents(mockServer, {});
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(0);
            expect(data.agents).toEqual([]);
        });
        it('should filter agents by name', async () => {
            const agents = [
                { id: 'agent-1', name: 'Production Agent', description: 'Production system' },
                { id: 'agent-2', name: 'Test Agent', description: 'Testing system' },
                { id: 'agent-3', name: 'Development Agent', description: 'Dev system' },
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'test' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(1);
            expect(data.agents).toHaveLength(1);
            expect(data.agents[0].name).toBe('Test Agent');
        });
        it('should filter agents by description', async () => {
            const agents = [
                { id: 'agent-1', name: 'Agent One', description: 'Production deployment' },
                { id: 'agent-2', name: 'Agent Two', description: 'Development environment' },
                { id: 'agent-3', name: 'Agent Three', description: 'Testing framework' },
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'development' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(1);
            expect(data.agents).toHaveLength(1);
            expect(data.agents[0].name).toBe('Agent Two');
        });
        it('should perform case-insensitive filtering', async () => {
            const agents = [
                { id: 'agent-1', name: 'UPPERCASE Agent', description: 'System ONE' },
                { id: 'agent-2', name: 'lowercase agent', description: 'system two' },
                { id: 'agent-3', name: 'MixedCase Agent', description: 'System Three' },
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'AGENT' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(3); // All agents match
            expect(data.agents).toHaveLength(3);
        });
        it('should handle agents without descriptions', async () => {
            const agents = [
                { id: 'agent-1', name: 'With Description', description: 'Has description' },
                { id: 'agent-2', name: 'Without Description', description: null },
                { id: 'agent-3', name: 'Undefined Description' }, // No description field
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'description' });
            const data = expectValidToolResponse(result);
            // All three agents match because they all have "Description" in their name
            expect(data.count).toBe(3);
            expect(data.agents.map((a) => a.name).sort()).toEqual([
                'Undefined Description',
                'With Description',
                'Without Description',
            ]);
        });
        it('should return multiple matches when filter matches multiple agents', async () => {
            const agents = [
                {
                    id: 'agent-1',
                    name: 'Customer Support Agent',
                    description: 'Handles customer queries',
                },
                { id: 'agent-2', name: 'Sales Agent', description: 'Manages customer sales' },
                { id: 'agent-3', name: 'Admin Agent', description: 'System administration' },
            ];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'customer' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(2);
            expect(data.agents).toHaveLength(2);
            expect(data.agents.map((a) => a.id).sort()).toEqual(['agent-1', 'agent-2']);
        });
        it('should handle undefined args gracefully', async () => {
            const agents = [fixtures.agent.basic];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, undefined);
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(1);
            expect(data.agents).toHaveLength(1);
        });
    });
    describe('Error Handling', () => {
        it('should handle API errors', async () => {
            const error = new Error('Network error');
            error.response = { status: 500, data: { error: 'Internal server error' } };
            mockServer.api.get.mockRejectedValueOnce(error);
            await expect(handleListAgents(mockServer, {})).rejects.toThrow('Network error');
        });
        it('should handle authentication errors', async () => {
            const error = new Error('Unauthorized');
            error.response = { status: 401, data: { error: 'Invalid credentials' } };
            mockServer.api.get.mockRejectedValueOnce(error);
            await expect(handleListAgents(mockServer, {})).rejects.toThrow('Unauthorized');
        });
        it('should handle malformed API response', async () => {
            // API returns non-array data
            mockServer.api.get.mockResolvedValueOnce({ data: { invalid: 'response' } });
            await expect(handleListAgents(mockServer, {})).rejects.toThrow();
        });
    });
    describe('Edge Cases', () => {
        it('should handle very large agent lists', async () => {
            // Create 1000 agents
            const agents = Array.from({ length: 1000 }, (_, i) => ({
                id: `agent-${i}`,
                name: `Agent ${i}`,
                description: `Description for agent ${i}`,
            }));
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, {});
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(1000);
            expect(data.agents).toHaveLength(1000);
        });
        it('should filter efficiently on large lists', async () => {
            const agents = Array.from({ length: 1000 }, (_, i) => ({
                id: `agent-${i}`,
                name: i % 10 === 0 ? `Special Agent ${i}` : `Regular Agent ${i}`,
                description: `Description ${i}`,
            }));
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: 'special' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(100); // Every 10th agent is special
            expect(data.agents).toHaveLength(100);
            data.agents.forEach((agent) => {
                expect(agent.name).toContain('Special');
            });
        });
        it('should handle empty filter string', async () => {
            const agents = [fixtures.agent.basic, fixtures.agent.minimal];
            mockServer.api.get.mockResolvedValueOnce({ data: agents });
            const result = await handleListAgents(mockServer, { filter: '' });
            const data = expectValidToolResponse(result);
            expect(data.count).toBe(2); // Empty filter matches all
            expect(data.agents).toHaveLength(2);
        });
    });
});