Skip to main content
Glama

YNAB MCP Server

by calebl
ApproveTransactionTool.test.ts10 kB
import { describe, it, expect, beforeEach, vi, Mock } from 'vitest'; import * as ynab from 'ynab'; import ApproveTransactionTool from '../tools/ApproveTransactionTool'; // Mock the entire ynab module vi.mock('ynab'); // Mock the mcp-framework logger vi.mock('mcp-framework', () => ({ MCPTool: class { constructor() {} }, logger: { error: vi.fn(), info: vi.fn(), }, })); describe('ApproveTransactionTool', () => { let tool: ApproveTransactionTool; let mockApi: { transactions: { getTransactionById: Mock; updateTransaction: Mock; }; }; beforeEach(() => { vi.clearAllMocks(); // Create mock API instance mockApi = { transactions: { getTransactionById: vi.fn(), updateTransaction: vi.fn(), }, }; // Mock the ynab.API constructor (ynab.API as any).mockImplementation(() => mockApi); // Set environment variables process.env.YNAB_API_TOKEN = 'test-token'; process.env.YNAB_BUDGET_ID = 'test-budget-id'; tool = new ApproveTransactionTool(); }); describe('execute', () => { const mockExistingTransaction = { id: 'transaction-123', account_id: 'account-456', payee_name: 'Test Payee', amount: -50000, approved: false, }; const mockUpdatedTransaction = { id: 'transaction-123', account_id: 'account-456', payee_name: 'Test Payee', amount: -50000, approved: true, }; it('should successfully approve a transaction with budget ID from input', async () => { // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); mockApi.transactions.updateTransaction.mockResolvedValue({ data: { transaction: mockUpdatedTransaction }, }); const input = { budgetId: 'custom-budget-id', transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(mockApi.transactions.getTransactionById).toHaveBeenCalledWith( 'custom-budget-id', 'transaction-123' ); expect(mockApi.transactions.updateTransaction).toHaveBeenCalledWith( 'custom-budget-id', 'transaction-123', { transaction: { approved: true, }, } ); expect(result).toEqual({ success: true, transactionId: 'transaction-123', message: 'Transaction updated successfully', }); }); it('should successfully approve a transaction with budget ID from environment', async () => { // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); mockApi.transactions.updateTransaction.mockResolvedValue({ data: { transaction: mockUpdatedTransaction }, }); const input = { transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(mockApi.transactions.getTransactionById).toHaveBeenCalledWith( 'test-budget-id', 'transaction-123' ); expect(mockApi.transactions.updateTransaction).toHaveBeenCalledWith( 'test-budget-id', 'transaction-123', { transaction: { approved: true, }, } ); expect(result).toEqual({ success: true, transactionId: 'transaction-123', message: 'Transaction updated successfully', }); }); it('should successfully disapprove a transaction', async () => { const mockDisapprovedTransaction = { ...mockExistingTransaction, approved: false, }; // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); mockApi.transactions.updateTransaction.mockResolvedValue({ data: { transaction: mockDisapprovedTransaction }, }); const input = { budgetId: 'test-budget-id', transactionId: 'transaction-123', approved: false, }; const result = await tool.execute(input); expect(mockApi.transactions.updateTransaction).toHaveBeenCalledWith( 'test-budget-id', 'transaction-123', { transaction: { approved: false, }, } ); expect(result).toEqual({ success: true, transactionId: 'transaction-123', message: 'Transaction updated successfully', }); }); it('should throw error when no budget ID is provided', async () => { // Clear environment budget ID delete process.env.YNAB_BUDGET_ID; tool = new ApproveTransactionTool(); const input = { transactionId: 'transaction-123', approved: true, }; await expect(tool.execute(input)).rejects.toThrow( 'No budget ID provided. Please provide a budget ID or set the YNAB_BUDGET_ID environment variable.' ); }); it('should handle transaction not found error', async () => { // Setup mock to return no transaction mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: null }, }); const input = { budgetId: 'test-budget-id', transactionId: 'nonexistent-transaction', approved: true, }; const result = await tool.execute(input); expect(result).toMatch(/Error getting unapproved transactions: Transaction not found/); }); it('should handle API error when getting existing transaction', async () => { // Setup mock to throw API error const apiError = new Error('API Error: Budget not found'); mockApi.transactions.getTransactionById.mockRejectedValue(apiError); const input = { budgetId: 'invalid-budget-id', transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(result).toMatch(/Error getting unapproved transactions: API Error: Budget not found/); }); it('should handle API error when updating transaction', async () => { // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); const apiError = new Error('API Error: Transaction update failed'); mockApi.transactions.updateTransaction.mockRejectedValue(apiError); const input = { budgetId: 'test-budget-id', transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(result).toMatch(/Error getting unapproved transactions: API Error: Transaction update failed/); }); it('should handle case when update returns no transaction data', async () => { // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); mockApi.transactions.updateTransaction.mockResolvedValue({ data: { transaction: null }, }); const input = { budgetId: 'test-budget-id', transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(result).toMatch(/Error getting unapproved transactions: Failed to update transaction - no transaction data returned/); }); it('should use default approved value of true when not specified', async () => { // Setup mocks mockApi.transactions.getTransactionById.mockResolvedValue({ data: { transaction: mockExistingTransaction }, }); mockApi.transactions.updateTransaction.mockResolvedValue({ data: { transaction: mockUpdatedTransaction }, }); const input = { budgetId: 'test-budget-id', transactionId: 'transaction-123', // approved not specified, should default to true }; const result = await tool.execute(input); expect(mockApi.transactions.updateTransaction).toHaveBeenCalledWith( 'test-budget-id', 'transaction-123', { transaction: { approved: undefined, // Will be undefined since not specified in input }, } ); expect(result).toEqual({ success: true, transactionId: 'transaction-123', message: 'Transaction updated successfully', }); }); it('should handle non-Error objects in catch block', async () => { // Setup mock to throw non-Error object const nonErrorObject = { message: 'Custom error object', code: 500 }; mockApi.transactions.getTransactionById.mockRejectedValue(nonErrorObject); const input = { budgetId: 'test-budget-id', transactionId: 'transaction-123', approved: true, }; const result = await tool.execute(input); expect(result).toMatch(/Error getting unapproved transactions: {"message":"Custom error object","code":500}/); }); }); describe('tool configuration', () => { it('should have correct name and description', () => { expect(tool.name).toBe('approve_transaction'); expect(tool.description).toBe('Approves an existing transaction in your YNAB budget.'); }); it('should have correct schema definition', () => { expect(tool.schema).toHaveProperty('budgetId'); expect(tool.schema).toHaveProperty('transactionId'); expect(tool.schema).toHaveProperty('approved'); expect(tool.schema.budgetId.description).toContain('budget containing the transaction'); expect(tool.schema.transactionId.description).toContain('id of the transaction to approve'); expect(tool.schema.approved.description).toContain('Whether the transaction should be marked as approved'); }); }); });

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/calebl/ynab-mcp-server'

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