UNPKG

@accounter/server

Version:
459 lines • 21.5 kB
import { describe, expect, it, vi } from 'vitest'; // Mock merge-charges helper before importing providers that use it vi.mock('@modules/charges/helpers/merge-charges.helper.js', () => ({ mergeChargesExecutor: vi.fn(), })); import { createMockTransaction, createMockDocument } from './test-helpers.js'; // Mock the module imports to avoid dependency issues vi.mock('graphql-modules', () => ({ Injectable: () => (target) => target, Inject: () => (target, propertyKey, parameterIndex) => { }, Injector: class { }, InjectionToken: class { description; constructor(description) { this.description = description; } }, Scope: { Operation: 'Operation' }, CONTEXT: Symbol('CONTEXT'), })); vi.mock('../../charges/providers/charges.provider.js', () => ({ ChargesProvider: class { }, })); vi.mock('../../documents/providers/documents.provider.js', () => ({ DocumentsProvider: class { }, })); vi.mock('../../transactions/providers/transactions.provider.js', () => ({ TransactionsProvider: class { }, })); vi.mock('../../financial-entities/providers/clients.provider.js', () => ({ ClientsProvider: class { }, })); vi.mock('../../admin-context/providers/admin-context.provider.js', () => ({ AdminContextProvider: class { }, })); vi.mock('../../../shared/helpers/index.js', () => ({ dateToTimelessDateString: (date) => date.toISOString().split('T')[0], })); const createMockProvider = (mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider) => { const mockAdminContextProvider = { getVerifiedAdminContext: vi.fn(() => Promise.resolve({ ownerId: ADMIN_BUSINESS_ID })), }; const mockCharges = mockChargesProvider ?? null; const mockTransactions = mockTransactionsProvider ?? null; const mockDocuments = mockDocumentsProvider ?? null; const mockInjector = { get: vi.fn((token) => { if (token.name === 'ChargesProvider') return mockCharges; if (token.name === 'TransactionsProvider') return mockTransactions; if (token.name === 'DocumentsProvider') return mockDocuments; if (token.name === 'ClientsProvider') return { getClientByIdLoader: { load: (businessId) => { const isRegisteredClient = businessId.startsWith('client-'); return Promise.resolve(isRegisteredClient ? { id: businessId } : null); }, }, }; return null; }), }; const mockContext = { injector: mockInjector, }; return new ChargesMatcherProvider(mockAdminContextProvider, mockCharges, mockTransactions, mockDocuments, mockContext); }; // Import after mocking const { ChargesMatcherProvider } = await import('../providers/charges-matcher.provider.js'); // Test constants const ADMIN_BUSINESS_ID = 'user-123'; // Mock charge object function createCharge(id, ownerId) { return { id, owner_id: ownerId, transactions_min_event_date: new Date('2024-01-15'), transactions_max_event_date: new Date('2024-01-15'), }; } describe('ChargesMatcherProvider - Integration Tests', () => { describe('findMatchesForCharge', () => { it('should find matches for transaction charge with mock data', async () => { // Setup mock data const sourceChargeId = 'tx-charge-1'; const sourceTransactions = [ createMockTransaction({ charge_id: sourceChargeId, amount: "100", currency: 'USD', event_date: new Date('2024-01-15'), }), ]; const candidateCharge1Id = 'doc-charge-1'; const candidateDocuments1 = [ createMockDocument({ charge_id: candidateCharge1Id, total_amount: 100, currency_code: 'USD', date: new Date('2024-01-15'), }), ]; const candidateCharge2Id = 'doc-charge-2'; const candidateDocuments2 = [ createMockDocument({ charge_id: candidateCharge2Id, total_amount: 110, currency_code: 'USD', date: new Date('2024-01-16'), }), ]; // Mock providers const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve(createCharge(id, ADMIN_BUSINESS_ID)); if (id === candidateCharge1Id) return Promise.resolve(createCharge(id, ADMIN_BUSINESS_ID)); if (id === candidateCharge2Id) return Promise.resolve(createCharge(id, ADMIN_BUSINESS_ID)); return Promise.resolve(new Error('Charge not found')); }), }, getChargesByFilters: vi.fn(() => Promise.resolve([ createCharge(candidateCharge1Id, ADMIN_BUSINESS_ID), createCharge(candidateCharge2Id, ADMIN_BUSINESS_ID), ])), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve(sourceTransactions); return Promise.resolve([]); }), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve([]); if (id === candidateCharge1Id) return Promise.resolve(candidateDocuments1); if (id === candidateCharge2Id) return Promise.resolve(candidateDocuments2); return Promise.resolve([]); }), }, }; // Execute const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); // Verify expect(result.matches).toHaveLength(2); expect(result.matches[0].chargeId).toBe(candidateCharge1Id); // Perfect match expect(result.matches[0].confidenceScore).toBeGreaterThan(0.95); expect(result.matches[1].chargeId).toBe(candidateCharge2Id); // Partial match expect(result.matches[1].confidenceScore).toBeLessThan(0.95); }); it('should find matches for document charge with mock data', async () => { // Setup mock data const sourceChargeId = 'doc-charge-1'; const sourceDocuments = [ createMockDocument({ charge_id: sourceChargeId, total_amount: 200, currency_code: 'USD', date: new Date('2024-02-15'), }), ]; const candidateCharge1Id = 'tx-charge-1'; const candidateTransactions1 = [ createMockTransaction({ charge_id: candidateCharge1Id, amount: "200", currency: 'USD', event_date: new Date('2024-02-15'), }), ]; const candidateCharge2Id = 'tx-charge-2'; const candidateTransactions2 = [ createMockTransaction({ charge_id: candidateCharge2Id, amount: "195", currency: 'USD', event_date: new Date('2024-02-16'), }), ]; // Mock providers const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve(createCharge(id, ADMIN_BUSINESS_ID)); return Promise.resolve(createCharge(id, ADMIN_BUSINESS_ID)); }), }, getChargesByFilters: vi.fn(() => Promise.resolve([ createCharge(candidateCharge1Id, ADMIN_BUSINESS_ID), createCharge(candidateCharge2Id, ADMIN_BUSINESS_ID), ])), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn((id) => { if (id === candidateCharge1Id) return Promise.resolve(candidateTransactions1); if (id === candidateCharge2Id) return Promise.resolve(candidateTransactions2); return Promise.resolve([]); }), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve(sourceDocuments); return Promise.resolve([]); }), }, }; // Execute const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); // Verify expect(result.matches).toHaveLength(2); expect(result.matches[0].chargeId).toBe(candidateCharge1Id); expect(result.matches[0].confidenceScore).toBeGreaterThan(0.95); }); it('should throw error if charge not found', async () => { const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(new Error('Not found'))), }, }; const provider = createMockProvider(mockChargesProvider); await expect(provider.findMatchesForCharge('non-existent')).rejects.toThrow(/Source charge not found/); }); it('should throw error if charge is already matched', async () => { const chargeId = 'matched-charge'; const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(chargeId, ADMIN_BUSINESS_ID))), }, }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn(() => Promise.resolve([createMockTransaction()])), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn(() => Promise.resolve([createMockDocument()])), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); await expect(provider.findMatchesForCharge(chargeId)).rejects.toThrow(); }); it('should throw error if charge has no transactions or documents', async () => { const chargeId = 'empty-charge'; const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(chargeId, ADMIN_BUSINESS_ID))), }, }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn(() => Promise.resolve([])), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn(() => Promise.resolve([])), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); await expect(provider.findMatchesForCharge(chargeId)).rejects.toThrow(/no transactions or documents/); }); it('should return empty matches if no candidates found', async () => { const sourceChargeId = 'tx-charge-only'; const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(sourceChargeId, ADMIN_BUSINESS_ID))), }, getChargesByFilters: vi.fn(() => Promise.resolve([])), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn(() => Promise.resolve([createMockTransaction()])), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn(() => Promise.resolve([])), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); expect(result.matches).toEqual([]); }); it('should filter out matched candidates (having both transactions and documents)', async () => { const sourceChargeId = 'tx-charge-1'; const matchedCandidateId = 'matched-charge'; const validCandidateId = 'doc-charge-1'; const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(sourceChargeId, ADMIN_BUSINESS_ID))), }, getChargesByFilters: vi.fn(() => Promise.resolve([ createCharge(matchedCandidateId, ADMIN_BUSINESS_ID), createCharge(validCandidateId, ADMIN_BUSINESS_ID), ])), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve([createMockTransaction()]); if (id === matchedCandidateId) return Promise.resolve([createMockTransaction()]); // Has transactions return Promise.resolve([]); }), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn((id) => { if (id === matchedCandidateId) return Promise.resolve([createMockDocument()]); // Also has documents - MATCHED if (id === validCandidateId) return Promise.resolve([createMockDocument()]); // Only documents - VALID return Promise.resolve([]); }), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); // Should only include validCandidateId, not matchedCandidateId expect(result.matches).toHaveLength(1); expect(result.matches[0].chargeId).toBe(validCandidateId); }); it('should handle date window filtering correctly', async () => { const sourceChargeId = 'tx-charge-1'; const sourceDate = new Date('2024-06-15'); const withinWindowId = 'doc-charge-within'; const outsideWindowId = 'doc-charge-outside'; const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(sourceChargeId, ADMIN_BUSINESS_ID))), }, getChargesByFilters: vi.fn(params => { // Verify date window parameters expect(params.fromAnyDate).toBeDefined(); expect(params.toAnyDate).toBeDefined(); return Promise.resolve([ createCharge(withinWindowId, ADMIN_BUSINESS_ID), createCharge(outsideWindowId, ADMIN_BUSINESS_ID), ]); }), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn((id) => { if (id === sourceChargeId) { return Promise.resolve([createMockTransaction({ event_date: sourceDate })]); } return Promise.resolve([]); }), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve([]); if (id === withinWindowId) { // Within 12-month window return Promise.resolve([ createMockDocument({ date: new Date('2024-06-20') }), ]); } if (id === outsideWindowId) { // Outside 12-month window return Promise.resolve([ createMockDocument({ date: new Date('2025-07-15') }), ]); } return Promise.resolve([]); }), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); // Should only include candidate within window expect(result.matches).toHaveLength(1); expect(result.matches[0].chargeId).toBe(withinWindowId); }); it('should throw error if user ID not in context', async () => { const mockAdminContextProvider = { getVerifiedAdminContext: vi.fn(() => Promise.reject(new Error('Admin context not found for the authenticated user.'))), }; const provider = new ChargesMatcherProvider(mockAdminContextProvider, null, null, null, { injector: {} }); await expect(provider.findMatchesForCharge('any-id')).rejects.toThrow(/Admin context not found/); }); it('should return top 5 matches when more candidates exist', async () => { const sourceChargeId = 'tx-charge-1'; // Create 7 candidate charges const candidateIds = Array.from({ length: 7 }, (_, i) => `doc-charge-${i + 1}`); const mockChargesProvider = { getChargeByIdLoader: { load: vi.fn(() => Promise.resolve(createCharge(sourceChargeId, ADMIN_BUSINESS_ID))), }, getChargesByFilters: vi.fn(() => Promise.resolve(candidateIds.map(id => createCharge(id, ADMIN_BUSINESS_ID)))), }; const mockTransactionsProvider = { transactionsByChargeIDLoader: { load: vi.fn((id) => { if (id === sourceChargeId) { return Promise.resolve([createMockTransaction({ amount: "100" })]); } return Promise.resolve([]); }), }, }; const mockDocumentsProvider = { getDocumentsByChargeIdLoader: { load: vi.fn((id) => { if (id === sourceChargeId) return Promise.resolve([]); // Create documents with varying amounts for different confidence scores const index = parseInt(id.split('-')[2]); return Promise.resolve([createMockDocument({ total_amount: 100 + index })]); }), }, }; const provider = createMockProvider(mockChargesProvider, mockTransactionsProvider, mockDocumentsProvider); const result = await provider.findMatchesForCharge(sourceChargeId); // Should return maximum of 5 matches expect(result.matches).toHaveLength(5); // Verify sorted by confidence (descending) for (let i = 0; i < result.matches.length - 1; i++) { expect(result.matches[i].confidenceScore).toBeGreaterThanOrEqual(result.matches[i + 1].confidenceScore); } }); }); }); //# sourceMappingURL=single-match-integration.test.js.map