Skip to main content
Glama
project-single.test.ts9.98 kB
/** * Tests for project_single tool * * Covers: * - Success scenarios with and without includes * - Error handling (not found, unauthorized, server errors) * - Input validation * - Edge cases (null fields, includes) */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { handleProjectSingle } from '../../../src/tools/project/project-single.js'; import { createMockClientWrapper } from '../../mocks/client.js'; import { createProject } from '../../mocks/factories/project.factory.js'; import type { MockFreshBooksClientWrapper } from '../../mocks/client.js'; describe('project_single tool', () => { let mockClientWrapper: MockFreshBooksClientWrapper; beforeEach(() => { mockClientWrapper = createMockClientWrapper(); vi.clearAllMocks(); }); describe('successful operations', () => { it('should retrieve a single project by ID', async () => { const project = createProject({ id: 12345, title: 'Test Project' }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 12345 }, { client: mockClientWrapper as any } ); expect(result.id).toBe(12345); expect(result.title).toBe('Test Project'); }); it('should handle project without wrapper object', async () => { const project = createProject({ id: 67890 }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce(project); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 67890 }, { client: mockClientWrapper as any } ); expect(result.id).toBe(67890); }); it('should retrieve project with client include', async () => { const project = createProject({ id: 99999 }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 99999, includes: ['client'] }, { client: mockClientWrapper as any } ); expect(result.id).toBe(99999); }); it('should retrieve project with services include', async () => { const project = createProject({ id: 11111, services: [{ id: 1, name: 'Development' }], } as any); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 11111, includes: ['services'] }, { client: mockClientWrapper as any } ); expect(result.id).toBe(11111); }); it('should retrieve project with group include', async () => { const project = createProject({ id: 22222, groupId: '555' } as any); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 22222, includes: ['group'] }, { client: mockClientWrapper as any } ); expect(result.id).toBe(22222); }); it('should retrieve project with multiple includes', async () => { const project = createProject({ id: 33333 }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 33333, includes: ['client', 'services', 'group'], }, { client: mockClientWrapper as any } ); expect(result.id).toBe(33333); }); it('should retrieve complete project details', async () => { const project = createProject({ id: 44444, title: 'Complete Project', description: 'Full description', clientId: '789', projectType: 'hourly_rate', billingMethod: 'project_rate', rate: '125.50', active: true, complete: false, }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 44444 }, { client: mockClientWrapper as any } ); expect(result.title).toBe('Complete Project'); expect(result.description).toBe('Full description'); expect(result.clientId).toBe('789'); expect(result.projectType).toBe('hourly_rate'); expect(result.billingMethod).toBe('project_rate'); expect(result.rate).toBe('125.50'); expect(result.active).toBe(true); expect(result.complete).toBe(false); }); }); describe('error handling', () => { it('should handle project not found error', async () => { const notFoundError = new Error('Project not found'); mockClientWrapper.executeWithRetry.mockRejectedValueOnce(notFoundError); await expect( handleProjectSingle( { accountId: 'ABC123', projectId: 99999 }, { client: mockClientWrapper as any } ) ).rejects.toThrow('Project not found'); }); it('should handle unauthorized error', async () => { const authError = new Error('Unauthorized'); mockClientWrapper.executeWithRetry.mockRejectedValueOnce(authError); await expect( handleProjectSingle( { accountId: 'ABC123', projectId: 12345 }, { client: mockClientWrapper as any } ) ).rejects.toThrow('Unauthorized'); }); it('should handle server error', async () => { const serverError = new Error('Internal Server Error'); mockClientWrapper.executeWithRetry.mockRejectedValueOnce(serverError); await expect( handleProjectSingle( { accountId: 'ABC123', projectId: 12345 }, { client: mockClientWrapper as any } ) ).rejects.toThrow('Internal Server Error'); }); it('should handle network timeout', async () => { const timeoutError = new Error('ETIMEDOUT'); mockClientWrapper.executeWithRetry.mockRejectedValueOnce(timeoutError); await expect( handleProjectSingle( { accountId: 'ABC123', projectId: 12345 }, { client: mockClientWrapper as any } ) ).rejects.toThrow('Request timed out'); }); }); describe('edge cases', () => { it('should handle project with all null optional fields', async () => { const project = createProject({ id: 55555, description: null, dueDate: null, clientId: null, budget: null, fixedPrice: null, rate: null, billingMethod: null, projectManagerId: null, retainerId: null, groupId: null, group: null, services: null, } as any); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 55555 }, { client: mockClientWrapper as any } ); expect(result.description).toBeNull(); expect(result.clientId).toBeNull(); expect(result.budget).toBeNull(); expect(result.rate).toBeNull(); }); it('should handle project with unicode in title', async () => { const project = createProject({ id: 66666, title: '日本語プロジェクト 🚀', description: 'Description with émojis and àccents', }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 66666 }, { client: mockClientWrapper as any } ); expect(result.title).toBe('日本語プロジェクト 🚀'); expect(result.description).toBe('Description with émojis and àccents'); }); it('should handle internal non-billable project', async () => { const project = createProject({ id: 77777, internal: true, billingMethod: null, clientId: null, } as any); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 77777 }, { client: mockClientWrapper as any } ); expect(result.internal).toBe(true); expect(result.billingMethod).toBeNull(); }); it('should handle completed archived project', async () => { const project = createProject({ id: 88888, active: false, complete: true, billedStatus: 'billed', }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 88888 }, { client: mockClientWrapper as any } ); expect(result.active).toBe(false); expect(result.complete).toBe(true); expect(result.billedStatus).toBe('billed'); }); it('should handle project with very large logged duration', async () => { const project = createProject({ id: 99999, loggedDuration: 9999999999, }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 99999 }, { client: mockClientWrapper as any } ); expect(result.loggedDuration).toBe(9999999999); }); it('should handle fixed-price project', async () => { const project = createProject({ id: 11111, projectType: 'fixed_price', billingMethod: 'flat_rate', fixedPrice: '50000.00', rate: null, }); mockClientWrapper.executeWithRetry.mockResolvedValueOnce({ project }); const result = await handleProjectSingle( { accountId: 'ABC123', projectId: 11111 }, { client: mockClientWrapper as any } ); expect(result.projectType).toBe('fixed_price'); expect(result.fixedPrice).toBe('50000.00'); expect(result.rate).toBeNull(); }); }); });

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/Good-Samaritan-Software-LLC/freshbooks-mcp'

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