import { jest } from '@jest/globals'; jest.unstable_mockModule('../../../database/database.js', () => ({ listObjects: jest.fn(), getObject: jest.fn(), editObject: jest.fn(), editObjects: jest.fn(), newObject: jest.fn(), deleteObject: jest.fn(), listObjectsByProperties: jest.fn(), getModelStats: jest.fn(), getModelHistory: jest.fn(), checkStates: jest.fn(), })); jest.unstable_mockModule('../../../database/schemas/finance/payment.schema.js', () => ({ paymentModel: { modelName: 'Payment' }, })); jest.unstable_mockModule('../../../database/schemas/finance/invoice.schema.js', () => ({ invoiceModel: { modelName: 'Invoice' }, })); jest.unstable_mockModule('log4js', () => ({ default: { getLogger: () => ({ level: 'info', debug: jest.fn(), error: jest.fn(), warn: jest.fn(), trace: jest.fn(), }), }, })); const { listPaymentsRouteHandler, getPaymentRouteHandler, newPaymentRouteHandler, postPaymentRouteHandler, authorisePaymentRouteHandler, declinePaymentRouteHandler, cancelPaymentRouteHandler, } = await import('../payments.js'); const { listObjects, getObject, editObject, newObject, checkStates } = await import( '../../../database/database.js' ); const { paymentModel } = await import('../../../database/schemas/finance/payment.schema.js'); const { invoiceModel } = await import('../../../database/schemas/finance/invoice.schema.js'); describe('Payment Service Route Handlers', () => { let req, res; beforeEach(() => { req = { params: {}, query: {}, body: {}, user: { id: 'test-user-id' }, }; res = { send: jest.fn(), status: jest.fn().mockReturnThis(), }; jest.clearAllMocks(); }); describe('listPaymentsRouteHandler', () => { it('should list payments', async () => { const mockResult = [{ _id: '1', amount: 100 }]; listObjects.mockResolvedValue(mockResult); await listPaymentsRouteHandler(req, res); expect(listObjects).toHaveBeenCalled(); expect(res.send).toHaveBeenCalledWith(mockResult); }); }); describe('newPaymentRouteHandler', () => { it('should create a new payment with invoice data', async () => { req.body = { invoice: 'inv123', amount: 100 }; const mockInvoice = { _id: 'inv123', vendor: { _id: 'v1' }, client: { _id: 'c1' } }; getObject.mockResolvedValueOnce(mockInvoice); const mockPayment = { _id: 'pay456', ...req.body }; newObject.mockResolvedValue(mockPayment); await newPaymentRouteHandler(req, res); expect(getObject).toHaveBeenCalledWith(expect.objectContaining({ id: 'inv123' })); expect(newObject).toHaveBeenCalled(); expect(res.send).toHaveBeenCalledWith(mockPayment); }); }); describe('postPaymentRouteHandler', () => { it('should post a draft payment', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(true); const mockPayment = { _id: '507f1f77bcf86cd799439011', invoice: 'inv123' }; getObject.mockResolvedValueOnce(mockPayment); getObject.mockResolvedValueOnce({ _id: 'inv123' }); editObject.mockResolvedValue({ _id: '507f1f77bcf86cd799439011', state: { type: 'posted' } }); await postPaymentRouteHandler(req, res); expect(checkStates).toHaveBeenCalledWith(expect.objectContaining({ states: ['draft'] })); expect(editObject).toHaveBeenCalled(); expect(res.send).toHaveBeenCalled(); }); it('should fail if payment is not in draft state', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(false); await postPaymentRouteHandler(req, res); expect(res.status).toHaveBeenCalledWith(400); expect(res.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'Payment is not in draft state.' }) ); }); }); describe('authorisePaymentRouteHandler', () => { it('should authorise a posted payment', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(true); editObject.mockResolvedValue({ _id: '507f1f77bcf86cd799439011', state: { type: 'authorised' }, }); await authorisePaymentRouteHandler(req, res); expect(checkStates).toHaveBeenCalledWith( expect.objectContaining({ states: ['posted'] }) ); expect(editObject).toHaveBeenCalledWith( expect.objectContaining({ updateData: expect.objectContaining({ state: { type: 'authorised' }, }), }) ); expect(res.send).toHaveBeenCalled(); }); it('should fail if payment is not in posted state', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(false); await authorisePaymentRouteHandler(req, res); expect(res.status).toHaveBeenCalledWith(400); expect(res.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'Payment is not in posted state.' }) ); }); }); describe('declinePaymentRouteHandler', () => { it('should decline a posted payment', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(true); editObject.mockResolvedValue({ _id: '507f1f77bcf86cd799439011', state: { type: 'declined' }, }); await declinePaymentRouteHandler(req, res); expect(checkStates).toHaveBeenCalledWith( expect.objectContaining({ states: ['posted'] }) ); expect(editObject).toHaveBeenCalledWith( expect.objectContaining({ updateData: expect.objectContaining({ state: { type: 'declined' }, }), }) ); expect(res.send).toHaveBeenCalled(); }); }); describe('cancelPaymentRouteHandler', () => { it('should cancel a posted payment', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(true); editObject.mockResolvedValue({ _id: '507f1f77bcf86cd799439011', state: { type: 'cancelled' }, }); await cancelPaymentRouteHandler(req, res); expect(checkStates).toHaveBeenCalledWith( expect.objectContaining({ states: ['posted'] }) ); expect(editObject).toHaveBeenCalled(); expect(res.send).toHaveBeenCalled(); }); it('should fail if payment is not in posted state', async () => { req.params.id = '507f1f77bcf86cd799439011'; checkStates.mockResolvedValue(false); await cancelPaymentRouteHandler(req, res); expect(res.status).toHaveBeenCalledWith(400); expect(res.send).toHaveBeenCalledWith( expect.objectContaining({ error: 'Payment is not in a cancellable state (must be posted).', }) ); }); }); });