Skip to main content
Glama
expense-create.test.ts10.3 kB
/** * Tests for expense_create tool */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { expenseCreateTool } from '../../../src/tools/expense/expense-create.js'; import { createMockClientWrapper } from '../../mocks/client.js'; import { mockExpenseCreateResponse, mockExpenseValidationError, } from '../../mocks/responses/expense.js'; import { mockUnauthorizedError, mockRateLimitError, mockServerError, } from '../../mocks/errors/freshbooks-errors.js'; describe('expense_create tool', () => { let mockClient: ReturnType<typeof createMockClientWrapper>; const validInput = { accountId: 'ABC123', categoryId: 5678, staffId: 1, date: '2024-01-15T00:00:00Z', amount: { amount: '150.00', code: 'USD', }, }; beforeEach(() => { mockClient = createMockClientWrapper(); vi.clearAllMocks(); }); describe('successful operations', () => { it('should create expense with required fields only', async () => { const mockResponse = mockExpenseCreateResponse({ categoryId: 5678, staffId: 1, amount: { amount: '150.00', code: 'USD' }, }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute(validInput, mockClient as any); expect(result.id).toBe(99999); expect(result.categoryId).toBe(5678); expect(result.staffId).toBe(1); }); it('should create expense with vendor and notes', async () => { const mockResponse = mockExpenseCreateResponse({ vendor: 'Office Depot', notes: 'Office supplies for Q1', }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, vendor: 'Office Depot', notes: 'Office supplies for Q1' }, mockClient as any ); expect(result.vendor).toBe('Office Depot'); expect(result.notes).toBe('Office supplies for Q1'); }); it('should create expense with client for billing', async () => { const mockResponse = mockExpenseCreateResponse({ clientId: 11111, }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, clientId: 11111 }, mockClient as any ); expect(result.clientId).toBe(11111); }); it('should create expense with project association', async () => { const mockResponse = mockExpenseCreateResponse({ projectId: 22222, }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, projectId: 22222 }, mockClient as any ); expect(result.projectId).toBe(22222); }); it('should create expense with markup percentage', async () => { const mockResponse = mockExpenseCreateResponse({ markupPercent: 15, clientId: 11111, }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, clientId: 11111, markupPercent: 15 }, mockClient as any ); expect(result.markupPercent).toBe(15); }); it('should create expense with taxes', async () => { const mockResponse = mockExpenseCreateResponse({ taxName1: 'GST', taxPercent1: '5', taxName2: 'PST', taxPercent2: '7', }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, taxName1: 'GST', taxPercent1: '5', taxName2: 'PST', taxPercent2: '7', }, mockClient as any ); expect(result.taxName1).toBe('GST'); expect(result.taxPercent1).toBe('5'); expect(result.taxName2).toBe('PST'); expect(result.taxPercent2).toBe('7'); }); it('should create expense with all optional fields', async () => { const mockResponse = mockExpenseCreateResponse({ vendor: 'Travel Agency', notes: 'Conference travel', clientId: 11111, projectId: 22222, markupPercent: 10, taxName1: 'GST', taxPercent1: '5', }); mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockResponse), }, }; return apiCall(client); }); const result = await expenseCreateTool.execute( { ...validInput, vendor: 'Travel Agency', notes: 'Conference travel', clientId: 11111, projectId: 22222, markupPercent: 10, taxName1: 'GST', taxPercent1: '5', }, mockClient as any ); expect(result.vendor).toBe('Travel Agency'); expect(result.notes).toBe('Conference travel'); expect(result.clientId).toBe(11111); expect(result.projectId).toBe(22222); expect(result.markupPercent).toBe(10); }); }); describe('error handling', () => { it('should handle unauthorized error', async () => { mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockUnauthorizedError()), }, }; return apiCall(client); }); await expect( expenseCreateTool.execute(validInput, mockClient as any) ).rejects.toThrow(); }); it('should handle rate limit error', async () => { mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockRateLimitError(60)), }, }; return apiCall(client); }); await expect( expenseCreateTool.execute(validInput, mockClient as any) ).rejects.toThrow(); }); it('should handle server error', async () => { mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue(mockServerError()), }, }; return apiCall(client); }); await expect( expenseCreateTool.execute(validInput, mockClient as any) ).rejects.toThrow(); }); it('should handle validation error for invalid category', async () => { mockClient.executeWithRetry.mockImplementation(async (operation, apiCall) => { const client = { expenses: { create: vi.fn().mockResolvedValue( mockExpenseValidationError('categoryId', 'Invalid category ID') ), }, }; return apiCall(client); }); await expect( expenseCreateTool.execute(validInput, mockClient as any) ).rejects.toThrow(); }); }); describe('input validation', () => { it('should require accountId', async () => { const input = { ...validInput }; delete (input as any).accountId; await expect( expenseCreateTool.execute(input as any, mockClient as any) ).rejects.toThrow(); }); it('should require categoryId', async () => { const input = { ...validInput }; delete (input as any).categoryId; await expect( expenseCreateTool.execute(input as any, mockClient as any) ).rejects.toThrow(); }); it('should require staffId', async () => { const input = { ...validInput }; delete (input as any).staffId; await expect( expenseCreateTool.execute(input as any, mockClient as any) ).rejects.toThrow(); }); it('should require date', async () => { const input = { ...validInput }; delete (input as any).date; await expect( expenseCreateTool.execute(input as any, mockClient as any) ).rejects.toThrow(); }); it('should require amount', async () => { const input = { ...validInput }; delete (input as any).amount; await expect( expenseCreateTool.execute(input as any, mockClient as any) ).rejects.toThrow(); }); it('should reject negative categoryId', async () => { await expect( expenseCreateTool.execute( { ...validInput, categoryId: -1 }, mockClient as any ) ).rejects.toThrow(); }); it('should reject markup percent over 100', async () => { await expect( expenseCreateTool.execute( { ...validInput, markupPercent: 101 }, mockClient as any ) ).rejects.toThrow(); }); it('should reject negative markup percent', async () => { await expect( expenseCreateTool.execute( { ...validInput, markupPercent: -5 }, mockClient as any ) ).rejects.toThrow(); }); }); });

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