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.

504 lines (407 loc) 16.2 kB
import { ApiClient } from '../src/app'; import { AuthProvider, Environment, AuthScopes, type InvoiceQueryResponse, Invoice } from '../src/app'; import { describe, expect, it, beforeEach, afterEach } from 'bun:test'; import { mockFetch, mockInvoiceData, mockTokenData, mockPDF } 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 Invoice API describe('Invoice 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 getAllInvoices Method describe('getAllInvoices', () => { // Describe the getAllInvoices Method it('should fetch all invoices', async () => { // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: mockInvoiceData, maxResults: mockInvoiceData.length, startPosition: 1, totalCount: mockInvoiceData.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getAllInvoices(); // Assert the Invoices expect(searchResponse.results).toBeArray(); // Assert the Invoices Length expect(searchResponse.results.length).toBe(mockInvoiceData.length); // Assert the Invoices expect(searchResponse.results[0].Id).toBe(mockInvoiceData[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 hasNextPage Method describe('hasNextPage', () => { // Describe the hasNextPage Method it('should return true if there is a next page', async () => { // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: mockInvoiceData, maxResults: mockInvoiceData.length, startPosition: 1, totalCount: mockInvoiceData.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getAllInvoices(); // Assert the Invoices 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 getInvoiceById Method describe('getInvoiceById', () => { // Describe the getInvoiceById Method it('should fetch single invoice by ID', async () => { // Get the Test Invoice const testInvoice = mockInvoiceData[0]; // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [testInvoice], maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock single invoice response structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoice const invoiceResponse = await apiClient.invoices.getInvoiceById(testInvoice.Id); // Assert the Invoice Response Structure expect(invoiceResponse).toBeObject(); expect(invoiceResponse).toHaveProperty('invoice'); expect(invoiceResponse).toHaveProperty('intuitTID'); expect(typeof invoiceResponse.intuitTID).toBe('string'); expect(invoiceResponse.intuitTID).toBe('test-tid-12345-67890'); // Assert the Invoice expect(invoiceResponse.invoice?.Id).toBe(testInvoice.Id); }); // Describe the getInvoiceById Method it('should throw error for invalid invoice ID', async () => { // Mock empty response with 400 status global.fetch = mockFetch( JSON.stringify({ QueryResponse: {}, fault: { error: [{ message: 'Invoice not found' }] }, }), 400, ); // Assert the Invoice expect(apiClient.invoices.getInvoiceById('invalid')).rejects.toThrow('Failed to run request'); }); }); // Describe the getInvoicesForDateRange Method describe('getInvoicesForDateRange', () => { // Describe the getInvoicesForDateRange Method it('should fetch invoices within date range', async () => { // Set the start Date const startDate = new Date('2025-01-09'); // Set the end Date const endDate = new Date('2025-01-12'); // Get the List of Invoices in that date range for the mock data const invoicesInDateRange = mockInvoiceData.filter((invoice) => { // Get the Invoice Date const invoiceDate = new Date(invoice.MetaData!.LastUpdatedTime!); // Return the Invoice if it is in the date range return invoiceDate >= startDate && invoiceDate <= endDate; }); // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: invoicesInDateRange, maxResults: mockInvoiceData.length, startPosition: 1, totalCount: mockInvoiceData.length, }, }; // Mock response with proper structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getInvoicesForDateRange(startDate, endDate); // Assert the Invoices expect(searchResponse.results).toBeArray(); // Assert the Invoices Length expect(searchResponse.results.length).toBe(invoicesInDateRange.length); // Assert the Invoices expect(searchResponse.results[0].Id).toBe(invoicesInDateRange[0].Id); }); }); // Describe the getUpdatedInvoices Method describe('getUpdatedInvoices', () => { // Describe the getUpdatedInvoices Method it('should fetch updated invoices', async () => { // Get the Last Updated Time const lastUpdatedTime = new Date('2025-01-09'); // Get the List of Invoices in that date range for the mock data const invoicesInDateRange = mockInvoiceData.filter((invoice) => { // Check if the last updated date is invalid if (!invoice.MetaData?.LastUpdatedTime) return false; // Get the Invoice Date const invoiceDate = new Date(invoice.MetaData.LastUpdatedTime); // Return the Invoice if it is in the date range return invoiceDate >= lastUpdatedTime; }); // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: invoicesInDateRange, maxResults: invoicesInDateRange.length, startPosition: 1, totalCount: invoicesInDateRange.length, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getUpdatedInvoices(lastUpdatedTime); // Assert the Invoices expect(searchResponse.results).toBeArray(); // Assert the Invoices Length expect(searchResponse.results.length).toBe(invoicesInDateRange.length); // Assert the Invoices expect(searchResponse.results[0].Id).toBe(invoicesInDateRange[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 getUpdatedInvoices Method it('should return an empty array if no invoices are updated', async () => { // Setup the Invoice Query Response const invoiceQueryResponse: { 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(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getUpdatedInvoices(new Date(new Date().getTime() + 68400000)); // Assert the Invoices expect(searchResponse.results).toBeArray(); // Assert the Invoices 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 getInvoicesByDueDate Method describe('getInvoicesByDueDate', () => { // Describe the getInvoicesByDueDate Method it('should fetch invoices by due date', async () => { // Set the Test Date const testDate = new Date('2024-12-23'); // Setup the Invoice Query Response const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: mockInvoiceData.filter((invoice) => invoice.DueDate && new Date(invoice.DueDate).getTime() === testDate.getTime()), maxResults: 1, startPosition: 1, totalCount: 1, }, }; // Mock the Fetch with proper QueryResponse structure global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); // Get the Invoices const searchResponse = await apiClient.invoices.getInvoicesByDueDate(testDate); // Assert the Invoices expect(searchResponse.results).toBeArray(); // Assert the Invoices Length expect(searchResponse.results.length).toBe(invoiceQueryResponse.QueryResponse.Invoice.length); // Assert the Invoices expect(new Date(searchResponse.results[0].DueDate).getTime()).toBe(testDate.getTime()); // Assert the Intuit TID expect(searchResponse.intuitTID).toBeDefined(); expect(typeof searchResponse.intuitTID).toBe('string'); expect(searchResponse.intuitTID).toBe('test-tid-12345-67890'); }); }); // Describe the Invoice Class Methods describe('Invoice Class', () => { afterEach(() => { global.fetch = globalFetch; }); it('should return Invoice class instance', async () => { const testInvoice = mockInvoiceData[0]; const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [testInvoice], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(testInvoice.Id); expect(invoiceResponse.invoice).toBeInstanceOf(Invoice); expect(typeof invoiceResponse.invoice?.setApiClient).toBe('function'); expect(typeof invoiceResponse.invoice?.reload).toBe('function'); expect(typeof invoiceResponse.invoice?.save).toBe('function'); expect(typeof invoiceResponse.invoice?.send).toBe('function'); expect(typeof invoiceResponse.invoice?.downloadPDF).toBe('function'); expect(typeof invoiceResponse.invoice?.void).toBe('function'); }); it('should send invoice via email', async () => { const testInvoice = mockInvoiceData[0]; const sentInvoice = { ...testInvoice, EmailStatus: 'EmailSent' }; // Setup initial fetch const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [testInvoice], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(testInvoice.Id); const invoice = invoiceResponse.invoice!; // Setup send response const sendResponse = { Invoice: [sentInvoice], }; global.fetch = mockFetch(JSON.stringify(sendResponse)); // Send the invoice await invoice.send(); // Assert the invoice was updated expect(invoice.EmailStatus).toBe(sentInvoice.EmailStatus); }); it('should throw error when sending invoice without ID', async () => { const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [mockInvoiceData[0]], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(mockInvoiceData[0].Id); const invoice = invoiceResponse.invoice!; // Remove ID to simulate unsaved invoice (invoice as any).Id = null; // Assert send throws error await expect(invoice.send()).rejects.toThrow('Invoice must be saved before sending'); }); it('should download invoice PDF', async () => { const testInvoice = mockInvoiceData[0]; const pdfBlob = new Blob(['PDF content'], { type: 'application/pdf' }); // Setup initial fetch const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [testInvoice], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(testInvoice.Id); const invoice = invoiceResponse.invoice!; // Setup PDF response global.fetch = mockPDF(pdfBlob); // Download the PDF const pdf = await invoice.downloadPDF(); // Assert the PDF is a Blob expect(pdf).toBeInstanceOf(Blob); expect(pdf.type).toBe('application/pdf'); }); it('should throw error when downloading PDF without ID', async () => { const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [mockInvoiceData[0]], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(mockInvoiceData[0].Id); const invoice = invoiceResponse.invoice!; // Remove ID to simulate unsaved invoice (invoice as any).Id = null; // Assert downloadPDF throws error await expect(invoice.downloadPDF()).rejects.toThrow('Invoice must be saved before downloading PDF'); }); it('should void invoice', async () => { const testInvoice = mockInvoiceData[0]; const voidedInvoice = { ...testInvoice, Balance: 0, SyncToken: '1' }; // Setup initial fetch const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [testInvoice], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(testInvoice.Id); const invoice = invoiceResponse.invoice!; // Setup void response const voidResponse = { Invoice: [voidedInvoice], }; global.fetch = mockFetch(JSON.stringify(voidResponse)); // Void the invoice await invoice.void(); // Assert the invoice was updated expect(invoice.Balance).toBe(voidedInvoice.Balance); }); it('should throw error when voiding invoice without ID', async () => { const invoiceQueryResponse: { QueryResponse: InvoiceQueryResponse } = { QueryResponse: { Invoice: [mockInvoiceData[0]], maxResults: 1, startPosition: 1, totalCount: 1, }, }; global.fetch = mockFetch(JSON.stringify(invoiceQueryResponse)); const invoiceResponse = await apiClient.invoices.getInvoiceById(mockInvoiceData[0].Id); const invoice = invoiceResponse.invoice!; // Remove ID to simulate unsaved invoice (invoice as any).Id = null; // Assert void throws error await expect(invoice.void()).rejects.toThrow('Invoice must be saved before voiding'); }); }); });