@accounter/server
Version:
Accounter GraphQL server
231 lines (204 loc) • 8.71 kB
text/typescript
import { Pool } from 'pg';
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 type {
IsracardTransactionInput,
PoalimForeignTransactionInput,
PoalimIlsTransactionInput,
PoalimSwiftTransactionInput,
} from '../../../../__generated__/types.js';
import { Currency } from '../../../../shared/enums.js';
import type { AuthContextProvider } from '../../../auth/providers/auth-context.provider.js';
import { DBProvider } from '../../../app-providers/db.provider.js';
import { TenantAwareDBClient } from '../../../app-providers/tenant-db-client.js';
import { PoalimScraperIngestionProvider } from '../poalim-scraper-ingestion.provider.js';
let pool: Pool;
let provider: PoalimScraperIngestionProvider;
const TRIGGER_TABLES = [
'poalim_ils_account_transactions',
'poalim_foreign_account_transactions',
'poalim_swift_account_transactions',
];
function createMockAuthContextProvider(): AuthContextProvider {
return {
getAuthContext: () =>
Promise.resolve({
authType: 'apiKey' as const,
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,
},
}),
} as unknown as AuthContextProvider;
}
beforeAll(async () => {
pool = await connectTestDb();
await runMigrationsIfNeeded(pool);
const dbProvider = new DBProvider(pool);
const dbClient = new TenantAwareDBClient(dbProvider, createMockAuthContextProvider());
provider = new PoalimScraperIngestionProvider(dbClient);
// 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`);
}
// Do NOT close db here — the shared pool is managed by vitest-global-setup teardown.
// Closing it here destroys the pool for all other concurrently-running integration test suites.
});
// ── Helpers ────────────────────────────────────────────────────────────────────
async function truncate(table: string) {
// Use DELETE instead of TRUNCATE CASCADE to avoid acquiring ACCESS EXCLUSIVE locks on
// tables with FK references (e.g. transactions_raw_list), which deadlocks with
// concurrently-running integration tests. Triggers are disabled in beforeAll so
// no dependent rows exist in other tables.
await pool.query(`DELETE FROM accounter_schema.${table}`);
}
// ── Poalim ILS ────────────────────────────────────────────────────────────────
describe('uploadPoalimIlsTransactions', () => {
beforeEach(() => truncate('poalim_ils_account_transactions'));
const baseTx: PoalimIlsTransactionInput = {
eventDate: '2024-01-15',
formattedEventDate: '15/01/2024',
serialNumber: 1,
activityTypeCode: 1,
activityDescription: 'Test txn',
textCode: 0,
referenceNumber: '1001',
referenceCatenatedNumber: 0,
valueDate: '2024-01-15',
formattedValueDate: '15/01/2024',
eventAmount: 500.00,
eventActivityTypeCode: 1,
currentBalance: 10000.00,
internalLinkCode: 0,
originalEventCreateDate: 0,
transactionType: 'NORMAL',
dataGroupCode: 0,
expandedEventDate: '20240115',
executingBranchNumber: 600,
eventId: '1',
differentDateIndication: 'N',
tableNumber: 0,
recordNumber: 0,
contraBankNumber: 0,
contraBranchNumber: 0,
contraAccountNumber: 0,
contraAccountTypeCode: 0,
marketingOfferContext: 0 as unknown as boolean,
commentExistenceSwitch: 0 as unknown as boolean,
fieldDescDisplaySwitch: 0 as unknown as boolean,
bankNumber: 12,
branchNumber: 600,
accountNumber: 100000,
};
it('inserts new transactions', async () => {
const result = await provider.uploadPoalimIlsTransactions([baseTx]);
expect(result.inserted).toBe(1);
expect(result.skipped).toBe(0);
expect(result.insertedIds).toHaveLength(1);
});
it('skips duplicate transactions (ON CONFLICT)', async () => {
await provider.uploadPoalimIlsTransactions([baseTx]);
const result = await provider.uploadPoalimIlsTransactions([baseTx]);
expect(result.inserted).toBe(0);
expect(result.skipped).toBe(1);
expect(result.insertedIds).toHaveLength(0);
});
it('returns correct counts for mixed new and duplicate', async () => {
await provider.uploadPoalimIlsTransactions([baseTx]);
const newTx: PoalimIlsTransactionInput = { ...baseTx, serialNumber: 2, referenceNumber: '1002' };
const result = await provider.uploadPoalimIlsTransactions([baseTx, newTx]);
expect(result.inserted).toBe(1);
expect(result.skipped).toBe(1);
});
it('returns empty result for empty input', async () => {
const result = await provider.uploadPoalimIlsTransactions([]);
expect(result).toEqual({ inserted: 0, skipped: 0, insertedIds: [], changedTransactions: [], insertedTransactions: [] });
});
});
// ── Poalim Foreign ────────────────────────────────────────────────────────────
describe('uploadPoalimForeignTransactions', () => {
beforeEach(() => truncate('poalim_foreign_account_transactions'));
const baseTx: PoalimForeignTransactionInput = {
executingDate: '2024-01-15',
formattedExecutingDate: '15/01/2024',
valueDate: '2024-01-15',
formattedValueDate: '15/01/2024',
originalSystemId: 0,
activityDescription: 'FX transfer',
eventAmount: 1000.00,
currency: Currency.Usd,
currentBalance: 5000.00,
referenceCatenatedNumber: 0,
referenceNumber: '7001',
currencyRate: 3.71,
rateFixingCode: 0,
contraCurrencyCode: 0,
eventActivityTypeCode: 1,
transactionType: 'NORMAL',
rateFixingShortDescription: '',
currencyLongDescription: 'US Dollar',
activityTypeCode: 1,
eventNumber: 1,
validityDate: '2024-01-15',
commentExistenceSwitch: 0 as unknown as boolean,
contraBankNumber: 0,
contraBranchNumber: 0,
contraAccountNumber: 0,
originalEventKey: 0 as unknown as boolean,
dataGroupCode: 0 as unknown as boolean,
rateFixingDescription: '',
currencySwiftCode: 'USD',
bankNumber: 12,
branchNumber: 600,
accountNumber: 100000,
};
it('inserts new foreign transactions', async () => {
const result = await provider.uploadPoalimForeignTransactions([baseTx]);
expect(result.inserted).toBe(1);
expect(result.skipped).toBe(0);
});
it('skips duplicate foreign transactions', async () => {
await provider.uploadPoalimForeignTransactions([baseTx]);
const result = await provider.uploadPoalimForeignTransactions([baseTx]);
expect(result.inserted).toBe(0);
expect(result.skipped).toBe(1);
});
});
// ── Poalim Swift ──────────────────────────────────────────────────────────────
describe('uploadPoalimSwiftTransactions', () => {
beforeEach(() => truncate('poalim_swift_account_transactions'));
const baseSwift: PoalimSwiftTransactionInput = {
transferCatenatedId: 'SWIFT-001',
accountNumber: 100000,
branchNumber: 600,
bankNumber: 12,
startDate: '20240115',
amount: 5000.00,
currencyCodeCatenatedKey: 'USD',
};
it('inserts new swift transactions', async () => {
const result = await provider.uploadPoalimSwiftTransactions([baseSwift]);
expect(result.inserted).toBe(1);
expect(result.skipped).toBe(0);
});
it('skips duplicate swift transactions by transferCatenatedId', async () => {
await provider.uploadPoalimSwiftTransactions([baseSwift]);
const result = await provider.uploadPoalimSwiftTransactions([baseSwift]);
expect(result.inserted).toBe(0);
expect(result.skipped).toBe(1);
});
});