From 6db1312fb10f1c8ef043ec09fcfeae9988626194 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 20 Aug 2026 15:02:47 +0300 Subject: [PATCH] test: cover portfolio import idempotency --- CHANGELOG.md | 6 ++++ backend/package.json | 3 +- .../services/portfolio.integration.test.ts | 29 +++++++++++++++++++ backend/src/services/portfolio.ts | 4 +-- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 backend/src/services/portfolio.integration.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c1232a2..5ecfebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Backend 0.8.2] - 2026-08-20 + +### Added + +- Added a database integration test for repeated portfolio imports and trade deduplication. + ## [Backend 0.8.1] - 2026-08-20 ### Fixed diff --git a/backend/package.json b/backend/package.json index 3a3aab9..8b96d02 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/backend", - "version": "0.8.1", + "version": "0.8.2", "private": true, "scripts": { "dev": "tsx watch src/app.ts", @@ -10,6 +10,7 @@ "migrate:prod": "node dist/db/migrate.js", "test:analytics": "tsx src/services/analyticsSemantics.test.ts", "test:portfolio": "tsx src/services/portfolio.test.ts", + "test:portfolio:db": "NODE_ENV=test tsx src/services/portfolio.integration.test.ts", "test:analytics:db": "NODE_ENV=test tsx src/services/analytics.integration.test.ts", "test:import:db": "NODE_ENV=test tsx src/services/import.integration.test.ts", "test:llm": "tsx src/scripts/testLlm.ts" diff --git a/backend/src/services/portfolio.integration.test.ts b/backend/src/services/portfolio.integration.test.ts new file mode 100644 index 0000000..e6cf4bf --- /dev/null +++ b/backend/src/services/portfolio.integration.test.ts @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import { pool } from '../db/pool'; +import { importPortfolio } from './portfolio'; + +const payload = { + schemaVersion: 'broker-portfolio-1.0', bank: 'PORTFOLIO_TEST', accountNumber: 'portfolio-test', + reportPeriod: { from: '2026-08-01', to: '2026-08-20' }, reportedAt: '2026-08-20T00:00:00+03:00', + positions: [{ instrument: 'Test Bond', isin: 'RU0000000001', quantity: '1.000', price: '100.123456', valuation: '100.123456' }], + trades: [{ instrument: 'Test Bond', isin: 'RU0000000001', concludedAt: '2026-08-10T10:00:00+03:00', side: 'Покупка', quantity: '1.000', settlementAmount: '100.123456' }], +}; + +async function run(): Promise { + try { + const first = await importPortfolio(payload); + const second = await importPortfolio(payload); + assert.equal(first.importedTrades, 1); + assert.equal(second.duplicateTrades, 1); + const rows = await pool.query('SELECT COUNT(*)::int AS count FROM portfolio_trades WHERE account_id = $1', [first.accountId]); + assert.equal(rows.rows[0].count, 1); + console.log('portfolio import SQL: OK'); + } finally { + await pool.query("DELETE FROM portfolio_trades WHERE account_id IN (SELECT id FROM accounts WHERE bank = 'PORTFOLIO_TEST' AND account_number = 'portfolio-test')"); + await pool.query("DELETE FROM portfolio_reports WHERE account_id IN (SELECT id FROM accounts WHERE bank = 'PORTFOLIO_TEST' AND account_number = 'portfolio-test')"); + await pool.query("DELETE FROM accounts WHERE bank = 'PORTFOLIO_TEST' AND account_number = 'portfolio-test'"); + await pool.end(); + } +} + +run(); diff --git a/backend/src/services/portfolio.ts b/backend/src/services/portfolio.ts index bdecec2..da406f1 100644 --- a/backend/src/services/portfolio.ts +++ b/backend/src/services/portfolio.ts @@ -43,11 +43,11 @@ export function validatePortfolio(body: unknown): asserts body is PortfolioFile } } -export async function importPortfolio(body: unknown): Promise { +export async function importPortfolio(body: unknown, db: Pick = pool): Promise { validatePortfolio(body); const data = body; const sourceHash = crypto.createHash('sha256').update(JSON.stringify(data)).digest('hex'); - const client = await pool.connect(); + const client = await db.connect(); try { await client.query('BEGIN'); const accountResult = await client.query(