UNPKG

@accounter/server

Version:
222 lines 9.68 kB
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; import { connectTestDb, closeTestDb } from '../../../../__tests__/helpers/db-connection.js'; import { runMigrationsIfNeeded } from '../../../../__tests__/helpers/db-migrations.js'; import { DBProvider } from '../../../app-providers/db.provider.js'; import { TenantAwareDBClient } from '../../../app-providers/tenant-db-client.js'; import { FiatExchangeProvider } from '../../../exchange-rates/providers/fiat-exchange.provider.js'; import { ScraperIngestionProvider } from '../scraper-ingestion.provider.js'; let pool; let provider; const TRIGGER_TABLES = [ 'cal_creditcard_transactions', 'bank_discount_transactions', 'max_creditcard_transactions', ]; function createMockAuthContextProvider() { return { getAuthContext: () => Promise.resolve({ authType: 'apiKey', token: 'test-token', tenant: { businessId: '00000000-0000-0000-0000-000000000000' }, user: { userId: 'api-key:test', auth0UserId: null, email: '', roleId: 'admin', permissions: [], emailVerified: true, permissionsVersion: 0, }, }), }; } beforeAll(async () => { pool = await connectTestDb(); await runMigrationsIfNeeded(pool); const dbProvider = new DBProvider(pool); const dbClient = new TenantAwareDBClient(dbProvider, createMockAuthContextProvider()); const fiatExchangeProvider = new FiatExchangeProvider(dbProvider); // Stub the DataLoader so it returns null for any date (no pre-existing rates in test DB) fiatExchangeProvider.getExchangeRatesByDatesLoader = { loadMany: async (dates) => dates.map(() => null), }; provider = new ScraperIngestionProvider(dbClient, fiatExchangeProvider); // Disable triggers so inserts don't cascade into charges/transactions, // which require financial_accounts and owner_id to be set up. for (const table of TRIGGER_TABLES) { await pool.query(`ALTER TABLE accounter_schema.${table} DISABLE TRIGGER ALL`); } }); afterAll(async () => { for (const table of TRIGGER_TABLES) { await pool.query(`ALTER TABLE accounter_schema.${table} ENABLE TRIGGER ALL`); } await closeTestDb(); }); // ── Helpers ──────────────────────────────────────────────────────────────────── async function truncate(table) { await pool.query(`TRUNCATE TABLE accounter_schema.${table} CASCADE`); } // ── Cal ─────────────────────────────────────────────────────────────────────── describe('uploadCalTransactions', () => { beforeEach(() => truncate('cal_creditcard_transactions')); const baseTx = { trnIntId: 'CAL-TXN-001', card: 4321, merchantName: 'Cal Store', trnAmt: '150.00', trnCurrencySymbol: 'ILS', debCrdCurrencySymbol: 'ILS', }; it('inserts new cal transactions', async () => { const result = await provider.uploadCalTransactions([baseTx]); expect(result.inserted).toBe(1); expect(result.skipped).toBe(0); }); it('skips duplicate cal transactions by trnIntId', async () => { await provider.uploadCalTransactions([baseTx]); const result = await provider.uploadCalTransactions([baseTx]); expect(result.inserted).toBe(0); expect(result.skipped).toBe(1); }); it('inserts two transactions with different trnIntId', async () => { const tx2 = { ...baseTx, trnIntId: 'CAL-TXN-002' }; const result = await provider.uploadCalTransactions([baseTx, tx2]); expect(result.inserted).toBe(2); expect(result.skipped).toBe(0); }); }); // ── Discount ────────────────────────────────────────────────────────────────── describe('uploadDiscountTransactions', () => { beforeEach(() => truncate('bank_discount_transactions')); const baseTx = { urn: 'DISCOUNT-URN-001', accountNumber: 'ACC-001', operationDate: '2024-01-15', operationAmount: '300.00', operationDescription: 'Transfer', }; it('inserts new discount transactions', async () => { const result = await provider.uploadDiscountTransactions([baseTx]); expect(result.inserted).toBe(1); expect(result.skipped).toBe(0); }); it('skips duplicate discount transactions by urn + accountNumber', async () => { await provider.uploadDiscountTransactions([baseTx]); const result = await provider.uploadDiscountTransactions([baseTx]); expect(result.inserted).toBe(0); expect(result.skipped).toBe(1); }); it('inserts same urn with different accountNumber', async () => { const tx2 = { ...baseTx, accountNumber: 'ACC-002' }; const result = await provider.uploadDiscountTransactions([baseTx, tx2]); expect(result.inserted).toBe(2); expect(result.skipped).toBe(0); }); }); // ── Max ─────────────────────────────────────────────────────────────────────── describe('uploadMaxTransactions', () => { beforeEach(() => truncate('max_creditcard_transactions')); const baseTx = { uid: 'MAX-UID-001', merchantName: 'Max Store', merchant: 'Max Store', originalAmount: '400.00', originalCurrency: 'ILS', purchaseDate: '2024-01-15', paymentDate: '2024-02-15', cardIndex: 1, categoryId: 100, planName: 'regular', planTypeId: 1, actualPaymentAmount: '400.00', arn: 'ARN-001', comments: '', dealDataAcq: '', dealDataAdjustmentType: '0', dealDataAmount: '400.00', dealDataAmountIls: '400.00', dealDataAmountLeft: '0', dealDataArn: 'ARN-001', dealDataAuthorizationNumber: '000000', dealDataCommissionVat: '0', dealDataExchangeDirect: 'N', dealDataExchangeRate: '1', dealDataInterestAmount: '0', dealDataIsAllowedSpreadWithBenefit: 0, dealDataIssuerCurrency: 'ILS', dealDataPlan: '0', dealDataPosEntryEmv: '0', dealDataProcessingDate: '2024-01-15', dealDataRefNbr: 'REF001', dealDataShowCancelDebit: 0, dealDataShowSpread: 0, dealDataShowSpreadBenefitButton: 0, dealDataShowSpreadButton: 0, dealDataShowSpreadForLeumi: 0, dealDataTdmCardToken: '', dealDataTdmTransactionType: 0, dealDataTransactionType: 0, dealDataTxnCode: 0, dealDataUserName: '', dealDataWithdrawalCommissionAmount: '0', ethocaInd: 0, isRegisterCh: 0, isSpreadingAutorizationAllowed: 0, issuerId: 0, merchantMaxPhone: 0, merchantNumber: '0', merchantPhone: '', merchantTaxId: '', promotionClub: '', refIndex: 0, runtimeReferenceInternalId: '', runtimeReferenceType: 0, spreadTransactionByCampainInd: 0, tableType: 0, userIndex: 0, }; it('inserts new max transactions', async () => { const result = await provider.uploadMaxTransactions([baseTx]); expect(result.inserted).toBe(1); expect(result.skipped).toBe(0); }); it('skips duplicate max transactions by uid', async () => { await provider.uploadMaxTransactions([baseTx]); const result = await provider.uploadMaxTransactions([baseTx]); expect(result.inserted).toBe(0); expect(result.skipped).toBe(1); }); }); // ── Currency Rates ──────────────────────────────────────────────────────────── describe('uploadCurrencyRates', () => { beforeEach(() => truncate('exchange_rates')); const baseRate = { exchangeDate: '2024-01-15', usd: 3.712, eur: 4.01, gbp: 4.65, }; it('inserts new currency rates', async () => { const result = await provider.uploadCurrencyRates([baseRate]); expect(result.inserted).toBe(1); expect(result.skipped).toBe(0); }); it('skips duplicate currency rates by exchangeDate', async () => { await provider.uploadCurrencyRates([baseRate]); const result = await provider.uploadCurrencyRates([baseRate]); expect(result.inserted).toBe(0); expect(result.skipped).toBe(1); }); it('inserts rates for different dates', async () => { const rate2 = { ...baseRate, exchangeDate: '2024-01-16', usd: 3.72 }; const result = await provider.uploadCurrencyRates([baseRate, rate2]); expect(result.inserted).toBe(2); expect(result.skipped).toBe(0); }); it('returns empty result for empty input', async () => { const result = await provider.uploadCurrencyRates([]); expect(result).toEqual({ inserted: 0, skipped: 0, insertedIds: [], changedTransactions: [], insertedTransactions: [] }); }); }); //# sourceMappingURL=scraper-ingestion.integration.test.js.map