'use strict'; /** * Integration tests – Invoice controller (controllers/invoice.js) * * We focus on the read path (getInvoices_get) which does not require the full * complex invoice-creation pipeline (jobs, clients, Stripe, etc.). * A seeded Invoice document is created directly via the model for query tests. */ const { connectDB, disconnectDB, clearCollection } = require('./jest.setup'); const { mockApplicator, mockClient, mockReq, mockRes, newId } = require('./mock_data'); let Invoice, Customer, Client; beforeAll(async () => { await connectDB(); Invoice = require('../../model/invoice'); Customer = require('../../model/customer'); Client = require('../../model/client'); }); afterAll(async () => { await disconnectDB(); }); const invoiceCtl = require('../../controllers/invoice'); describe('Invoice controller – data methods', () => { let applicator, client; function makeInvoiceDoc(puid, clientId) { return { code: `INV-${Date.now()}`, companyName: 'Test Applicator Co.', address: '123 Main St', currency: 'USD', dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), openDate: new Date(), status: 'open', paymentTerm: 30, byPuid: puid, clients: [ { billTo: clientId, code: `INV-${Date.now()}-1`, split: '100', subTotal: '100.00', }, ], jobs: [], }; } beforeAll(async () => { await clearCollection(Invoice); applicator = await Customer.create(mockApplicator()); client = await Client.create(mockClient(applicator._id)); }); afterAll(async () => { await clearCollection(Invoice); await Client.deleteMany({ _id: client._id }); await Customer.deleteMany({ _id: applicator._id }); }); const makeReq = (extra = {}) => mockReq({ uid: applicator._id, puid: applicator._id, ut: '1', userInfo: { puid: applicator._id, kind: '1', premium: 0, membership: null, markedDelete: false, }, ...extra, }); // ------------------------------------------------------------------------- describe('getInvoices_get', () => { beforeAll(async () => { await Invoice.create(makeInvoiceDoc(applicator._id, client._id)); }); it('returns invoices for the applicator', async () => { const req = makeReq({ query: {} }); const res = mockRes(); await invoiceCtl.getInvoices_get(req, res); expect(res.json).toHaveBeenCalled(); expect(Array.isArray(res._data)).toBe(true); expect(res._data.length).toBeGreaterThan(0); }); it('returns empty array when no invoices match filter', async () => { const filters = JSON.stringify({ code: { value: 'NONEXISTENT_CODE_12345', valueOperator: 'contains' } }); const req = makeReq({ query: { filters } }); const res = mockRes(); await invoiceCtl.getInvoices_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data.length).toBe(0); }); it('throws AppParamError when applicator puid is invalid', async () => { const req = mockReq({ uid: newId(), puid: 'invalid-puid', ut: '1', userInfo: { puid: 'invalid-puid', kind: '1', premium: 0, membership: null, markedDelete: false, }, }); const res = mockRes(); await expect(invoiceCtl.getInvoices_get(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('getInvoiceById_get', () => { let invoiceId, InvoiceSetting; beforeAll(async () => { InvoiceSetting = require('../../model/invoice_settings'); // Seed a default invoice issuer setting (required by getInvoiceById_get) await InvoiceSetting.findOneAndUpdate( { userId: null, byPuid: applicator._id }, { $setOnInsert: { userId: null, byPuid: applicator._id, companyName: 'Test Co.', paymentTerm: 30 } }, { upsert: true, new: true } ); const doc = await Invoice.create(makeInvoiceDoc(applicator._id, client._id)); invoiceId = String(doc._id); }); it('returns invoice detail with issuer info', async () => { const req = makeReq({ params: { id: invoiceId } }); const res = mockRes(); await invoiceCtl.getInvoiceById_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toBeTruthy(); }); it('throws when invoice is not found', async () => { const req = makeReq({ params: { id: String(newId()) } }); const res = mockRes(); await expect(invoiceCtl.getInvoiceById_get(req, res)).rejects.toThrow(); }); }); // ------------------------------------------------------------------------- describe('getPrintInformation_get', () => { let invoiceId, InvoiceSetting; beforeAll(async () => { InvoiceSetting = require('../../model/invoice_settings'); await InvoiceSetting.findOneAndUpdate( { userId: null, byPuid: applicator._id }, { $setOnInsert: { userId: null, byPuid: applicator._id, companyName: 'Test Co.', paymentTerm: 30 } }, { upsert: true, new: true } ); const doc = await Invoice.create(makeInvoiceDoc(applicator._id, client._id)); invoiceId = String(doc._id); }); it('throws when invoiceId is missing', async () => { const req = makeReq({ query: { clientId: String(client._id) }, }); const res = mockRes(); await expect(invoiceCtl.getPrintInformation_get(req, res)).rejects.toThrow(); }); it('throws when invoice is not found for the given client', async () => { const req = makeReq({ query: { invoiceId: String(newId()), clientId: String(client._id) }, }); const res = mockRes(); await expect(invoiceCtl.getPrintInformation_get(req, res)).rejects.toThrow(); }); it('returns print information for a valid invoice and client', async () => { const req = makeReq({ query: { invoiceId, clientId: String(client._id) }, }); const res = mockRes(); await invoiceCtl.getPrintInformation_get(req, res); expect(res.json).toHaveBeenCalled(); expect(res._data).toBeTruthy(); }); }); // ------------------------------------------------------------------------- describe('exportInvoices_get', () => { it('throws when exportType is missing', async () => { const req = makeReq({ query: { invoiceIds: [String(newId())] } }); const res = mockRes(); await expect(invoiceCtl.exportInvoices_get(req, res)).rejects.toThrow(); }); it('throws when invoiceIds is empty', async () => { const req = makeReq({ query: { exportType: 'csv', invoiceIds: [] } }); const res = mockRes(); await expect(invoiceCtl.exportInvoices_get(req, res)).rejects.toThrow(); }); }); });