diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ecfebd..faec18f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Backend 0.8.3] - 2026-08-20 + +### Fixed + +- Invalid non-string trade identifiers now return validation errors instead of server errors. + ## [Backend 0.8.2] - 2026-08-20 ### Added diff --git a/backend/package.json b/backend/package.json index 8b96d02..7dbc83a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "@family-budget/backend", - "version": "0.8.2", + "version": "0.8.3", "private": true, "scripts": { "dev": "tsx watch src/app.ts", diff --git a/backend/src/services/portfolio.test.ts b/backend/src/services/portfolio.test.ts index 3ad6c11..ef5f73e 100644 --- a/backend/src/services/portfolio.test.ts +++ b/backend/src/services/portfolio.test.ts @@ -8,5 +8,6 @@ const trade = { assert.equal(deriveTradeSourceId(trade), 'RU0000000001|2026-08-20T10:00:00+03:00|Покупка|1.000|1234.50'); assert.throws(() => validatePortfolio(null), /object/); +assert.throws(() => validatePortfolio({ schemaVersion: 'broker-portfolio-1.0', bank: 'B', accountNumber: 'A', reportPeriod: { from: '2026-08-20', to: '2026-08-20' }, reportedAt: null, positions: [], trades: [{ ...trade, sourceId: 42 }] }), /sourceId/); assert.throws(() => validatePortfolio({ ...{ schemaVersion: 'broker-portfolio-1.0', bank: 'B', accountNumber: 'A', reportPeriod: { from: '2026-08-21', to: '2026-08-20' }, reportedAt: null, positions: [], trades: [] } }), /period/); console.log('portfolio validation: OK'); diff --git a/backend/src/services/portfolio.ts b/backend/src/services/portfolio.ts index da406f1..19ceb3c 100644 --- a/backend/src/services/portfolio.ts +++ b/backend/src/services/portfolio.ts @@ -29,6 +29,8 @@ export function validatePortfolio(body: unknown): asserts body is PortfolioFile const sourceIds = new Set(); for (const trade of data.trades) { if (!trade || typeof trade.instrument !== 'string' || !trade.instrument.trim() || typeof trade.concludedAt !== 'string' || Number.isNaN(Date.parse(trade.concludedAt)) || typeof trade.side !== 'string' || !trade.side.trim()) throw new Error('Invalid trade'); + if (trade.sourceId !== undefined && typeof trade.sourceId !== 'string') throw new Error('sourceId must be a string'); + if (trade.operationId !== undefined && typeof trade.operationId !== 'string') throw new Error('operationId must be a string'); const sourceId = deriveTradeSourceId(trade); if (sourceIds.has(sourceId)) throw new Error(`Duplicate sourceId: ${sourceId}`); sourceIds.add(sourceId);