UNPKG

quickbooks-api

Version:

A modular TypeScript SDK for seamless integration with Intuit QuickBooks APIs. Provides robust authentication handling and future-ready foundation for accounting, payments, and commerce operations.

533 lines (434 loc) 16.2 kB
import { ApiClient } from '../src/app'; import { AuthProvider, Environment, AuthScopes, type AccountQueryResponse, Account } from '../src/app'; import { describe, expect, it, beforeEach, afterEach } from 'bun:test'; import { mockFetch, mockAccountData, mockTokenData } from './helpers'; // Mock configuration const TEST_CONFIG = { clientId: 'test_client_id', clientSecret: 'test_client_secret', redirectUri: 'http://localhost:3000/auth-code', scopes: [AuthScopes.Accounting], }; // Describe the Account API describe('Account API', () => { // Declare the API Client let apiClient: ApiClient; // Declare the Global Fetch let globalFetch: typeof fetch; // Before Each beforeEach(async () => { // Set the Global Fetch globalFetch = global.fetch; // Create the Auth Provider const authProvider = new AuthProvider(TEST_CONFIG.clientId, TEST_CONFIG.clientSecret, TEST_CONFIG.redirectUri, TEST_CONFIG.scopes); // Set the Token for the Auth Provider await authProvider.setToken(mockTokenData); // Create the API Client apiClient = new ApiClient(authProvider, Environment.Sandbox); }); // After Each afterEach(() => { // Set the Global Fetch global.fetch = globalFetch; }); // Describe the getAllAccounts Method describe('getAllAccounts', () => { // After Each afterEach(() => { // Set the Global Fetch global.fetch = globalFetch; }); // Test handling search options it('should handle search options', async () => { // Get a subset of accounts const accounts = mockAccountData.slice(0, 10); // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: accounts, maxResults: 10, startPosition: 1, totalCount: accounts.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts with search options const searchResponse = await apiClient.accounts.getAllAccounts({ maxResults: 10, page: 1, }); // Assert the Accounts expect(searchResponse.results).toBeArray(); expect(searchResponse.results.length).toBe(10); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); // Test fetching all accounts it('should fetch all accounts', async () => { // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: mockAccountData, maxResults: mockAccountData.length, startPosition: 1, totalCount: mockAccountData.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts const searchResponse = await apiClient.accounts.getAllAccounts(); // Assert the Accounts expect(searchResponse.results).toBeArray(); expect(searchResponse.results.length).toBe(mockAccountData.length); searchResponse.results.forEach((account, index) => { expect(account.Id).toBe(mockAccountData[index].Id); }); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the hasNextPage Method describe('hasNextPage', () => { it('should return true if there is a next page', async () => { // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: mockAccountData, maxResults: mockAccountData.length, startPosition: 1, totalCount: mockAccountData.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts const searchResponse = await apiClient.accounts.getAllAccounts(); // Assert the Next Page expect(searchResponse.hasNextPage).toBe(true); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the getAccountById Method describe('getAccountById', () => { // After Each afterEach(() => { // Set the Global Fetch global.fetch = globalFetch; }); it('should fetch accounts by their IDs', async () => { // Test each account in mock data for (const account of mockAccountData) { // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: [account], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock single account response structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Account const accountResponse = await apiClient.accounts.getAccountById(account.Id); // Assert the Account Response Structure expect(accountResponse).toBeObject(); expect(accountResponse).toHaveProperty('account'); expect(accountResponse).toHaveProperty('intuitTID'); expect(typeof accountResponse.intuitTID).toBe('string'); expect(accountResponse.intuitTID).toBe('test-tid-12345-67890'); // Assert the Account expect(accountResponse.account).toBeObject(); expect(accountResponse.account?.Id).toBe(account.Id); // Assert the Account is a class instance expect(accountResponse.account).toBeInstanceOf(Account); expect(typeof accountResponse.account?.setApiClient).toBe('function'); expect(typeof accountResponse.account?.reload).toBe('function'); expect(typeof accountResponse.account?.save).toBe('function'); } }); it('should throw error for invalid account ID', async () => { // Mock empty response with 400 status global.fetch = mockFetch( JSON.stringify({ QueryResponse: {}, fault: { error: [{ message: 'Account not found' }] }, }), 400, ); // Assert the Error expect(apiClient.accounts.getAccountById('-1')).rejects.toThrow('Failed to run request'); }); }); // Describe the getAccountsForDateRange Method describe('getAccountsForDateRange', () => { it('should fetch accounts within date range', async () => { // Set the start Date const startDate = new Date('2020-01-09'); // Set the end Date const endDate = new Date('2025-01-12'); // Get the List of Accounts in that date range for the mock data const accountsInDateRange = mockAccountData.filter((account) => { // Skip if no metadata if (!account.MetaData?.LastUpdatedTime) return false; const accountDate = new Date(account.MetaData.LastUpdatedTime); return accountDate >= startDate && accountDate <= endDate; }); // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: accountsInDateRange, maxResults: accountsInDateRange.length, startPosition: 1, totalCount: accountsInDateRange.length, }, }; // Mock response with proper structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts const searchResponse = await apiClient.accounts.getAccountsForDateRange(startDate, endDate); // Assert the Accounts expect(searchResponse.results).toBeArray(); expect(searchResponse.results.length).toBe(accountsInDateRange.length); expect(searchResponse.results[0].Id).toBe(accountsInDateRange[0].Id); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the getUpdatedAccounts Method describe('getUpdatedAccounts', () => { it('should fetch updated accounts', async () => { // Get the Last Updated Time const lastUpdatedTime = new Date('2024-01-09'); // Get the List of Updated Accounts from mock data const updatedAccounts = mockAccountData.filter((account) => { // Skip if no metadata if (!account.MetaData?.LastUpdatedTime) return false; const accountDate = new Date(account.MetaData.LastUpdatedTime); return accountDate >= lastUpdatedTime; }); // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: updatedAccounts, maxResults: updatedAccounts.length, startPosition: 1, totalCount: updatedAccounts.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts const searchResponse = await apiClient.accounts.getUpdatedAccounts(lastUpdatedTime); // Assert the Accounts expect(searchResponse.results).toBeArray(); expect(searchResponse.results.length).toBe(updatedAccounts.length); expect(searchResponse.results[0].Id).toBe(updatedAccounts[0].Id); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); // Describe the getUpdatedCustomers Method it('should return an empty array if no customers are updated', async () => { // Setup the Account Query Response const accountQueryResponse: { QueryResponse: {}; time: string } = { QueryResponse: {}, time: '2025-03-04T05:46:36.933-08:00', }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Accounts const searchResponse = await apiClient.accounts.getUpdatedAccounts(new Date(new Date().getTime() + 68400000)); // Assert the Accounts expect(searchResponse.results).toBeArray(); // Assert the Accounts Length expect(searchResponse.results.length).toBe(0); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the rawAccountQuery Method describe('rawAccountQuery', () => { it('should execute raw account query', async () => { // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: mockAccountData, maxResults: mockAccountData.length, startPosition: 1, totalCount: mockAccountData.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Query Builder const queryBuilder = await apiClient.accounts.getQueryBuilder(); // Execute Raw Query const searchResponse = await apiClient.accounts.rawAccountQuery(queryBuilder); // Assert the Accounts expect(searchResponse.results).toBeArray(); expect(searchResponse.results.length).toBe(mockAccountData.length); expect(searchResponse.results[0].Id).toBe(mockAccountData[0].Id); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the Account Class Methods describe('Account Class', () => { // After Each afterEach(() => { // Set the Global Fetch global.fetch = globalFetch; }); // Test class instance methods it('should have class instance methods', async () => { // Get a test account const testAccount = mockAccountData[0]; // Setup the Account Query Response const accountQueryResponse = { QueryResponse: { Account: [testAccount], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Account const accountResponse = await apiClient.accounts.getAccountById(testAccount.Id); // Assert it's a class instance expect(accountResponse.account).toBeInstanceOf(Account); // Test setApiClient method exists and is callable expect(typeof accountResponse.account?.setApiClient).toBe('function'); expect(() => accountResponse.account?.setApiClient(apiClient)).not.toThrow(); // Test reload method exists expect(typeof accountResponse.account?.reload).toBe('function'); // Test save method exists expect(typeof accountResponse.account?.save).toBe('function'); // Test delete method exists expect(typeof accountResponse.account?.delete).toBe('function'); }); // Test reload method it('should reload account data', async () => { // Get a test account const testAccount = mockAccountData[0]; const updatedAccount = { ...testAccount, Name: 'Updated Account Name' }; // Setup the Account Query Response for initial fetch const accountQueryResponse = { QueryResponse: { Account: [testAccount], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch for initial fetch global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Account const accountResponse = await apiClient.accounts.getAccountById(testAccount.Id); const account = accountResponse.account!; // Modify the account locally (account as any).Name = 'Local Change'; // Setup the Account Query Response for reload const reloadQueryResponse = { QueryResponse: { Account: [updatedAccount], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch for reload global.fetch = mockFetch(JSON.stringify(reloadQueryResponse)); // Reload the Account await account.reload(); // Assert the Account was reloaded expect(account.Name).toBe(updatedAccount.Name); }); // Test save method it('should save account data', async () => { // Get a test account const testAccount = mockAccountData[0]; const savedAccount = { ...testAccount, SyncToken: '1', Id: testAccount.Id }; // Setup the Account Query Response for initial fetch const accountQueryResponse = { QueryResponse: { Account: [testAccount], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch for initial fetch global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Account const accountResponse = await apiClient.accounts.getAccountById(testAccount.Id); const account = accountResponse.account!; // Modify the account account.Name = 'Updated Account Name'; // Setup the response for save const saveResponse = { QueryResponse: { Account: [savedAccount], }, }; // Mock the Fetch for save global.fetch = mockFetch(JSON.stringify(saveResponse)); // Save the Account await account.save(); // Assert the save was called (mock was invoked) expect(global.fetch).toBeDefined(); }); // Test delete method it('should delete (deactivate) account by setting Active=false', async () => { // Get a test account const testAccount = mockAccountData[0]; const deletedAccount = { ...testAccount, Active: false, SyncToken: '1' }; // Setup the Account Query Response for initial fetch const accountQueryResponse = { QueryResponse: { Account: [testAccount], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch for initial fetch global.fetch = mockFetch(JSON.stringify(accountQueryResponse)); // Get the Account const accountResponse = await apiClient.accounts.getAccountById(testAccount.Id); const account = accountResponse.account!; // Ensure account is active account.Active = true; // Setup the response for delete (save with Active=false) const deleteResponse = { QueryResponse: { Account: [deletedAccount], }, }; // Mock the Fetch for delete global.fetch = mockFetch(JSON.stringify(deleteResponse)); // Delete the Account await account.delete(); // Assert the account was deactivated expect(account.Active).toBe(false); }); }); });