fix: harden auth and investment synchronization
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s
Resolve A01-A10 and C01-C07 from the backend audit. Preserve auth failures, forced candle reconciliation and forecast freshness. Validate MOEX responses and nested HTTP errors; remove unused legacy paths. Verified: 209 unit tests, 262 SQLite e2e tests on Linux, OpenAPI and durability. Record the remaining macOS native SQLite abort in the audit.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
PortfolioDto,
|
||||
PortfolioPositionDto,
|
||||
} from '../../portfolio/presentation/portfolio.dto';
|
||||
import { instrumentPosition } from './instrument-position';
|
||||
|
||||
const money = (value: string, currency = 'RUB') => ({ value, currency });
|
||||
const row = (
|
||||
quantity = '2',
|
||||
average = '10',
|
||||
value = '30',
|
||||
uid = 'common',
|
||||
): PortfolioPositionDto => ({
|
||||
instrumentUid: uid,
|
||||
figi: uid,
|
||||
displayName: uid,
|
||||
type: 'STOCK',
|
||||
quantity,
|
||||
averagePurchasePrice: money(average),
|
||||
currentValue: money(value),
|
||||
absoluteReturn: money('10'),
|
||||
isin: null,
|
||||
ticker: null,
|
||||
logoName: null,
|
||||
});
|
||||
const portfolio = (positions: PortfolioPositionDto[]): PortfolioDto => ({
|
||||
positions,
|
||||
cashBalances: [],
|
||||
totalsByCurrency: [],
|
||||
isStale: false,
|
||||
lastSuccessfulAt: '2026-09-10T00:00:00Z',
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
});
|
||||
|
||||
describe('stock position', () => {
|
||||
it('weights costs exactly and keeps preferred shares separate', () => {
|
||||
const result = instrumentPosition(
|
||||
portfolio([
|
||||
row(),
|
||||
row('3', '20', '70'),
|
||||
row('100', '1', '100', 'preferred'),
|
||||
]),
|
||||
'common',
|
||||
'STOCK',
|
||||
);
|
||||
expect(result.quantity).toBe('5');
|
||||
expect(result.averagePurchasePrice).toEqual(money('16'));
|
||||
expect(result.currentValue).toEqual(money('100'));
|
||||
expect(result.absoluteReturn).toEqual(money('20'));
|
||||
expect(result.returnPercent).toBe('25');
|
||||
expect(result.accounts[0].returnPercent).toBe('50');
|
||||
});
|
||||
it.each(['9007199254740993', '0.25'])(
|
||||
'preserves exact quantity %s',
|
||||
(quantity) => {
|
||||
const result = instrumentPosition(
|
||||
portfolio([row(quantity, '0.1', '900719925474099.3')]),
|
||||
'common',
|
||||
'STOCK',
|
||||
);
|
||||
expect(result.quantity).toBe(quantity);
|
||||
expect(result.averagePurchasePrice?.value).toBe('0.1');
|
||||
},
|
||||
);
|
||||
it('does not sum currencies or treat unknown values as zero', () => {
|
||||
const second = {
|
||||
...row(),
|
||||
currentValue: money('2', 'USD'),
|
||||
averagePurchasePrice: null,
|
||||
absoluteReturn: null,
|
||||
};
|
||||
const result = instrumentPosition(
|
||||
portfolio([row(), second]),
|
||||
'common',
|
||||
'STOCK',
|
||||
);
|
||||
expect(result.quantity).toBe('4');
|
||||
expect(result.currentValue).toBeNull();
|
||||
expect(result.averagePurchasePrice).toBeNull();
|
||||
expect(result.absoluteReturn).toBeNull();
|
||||
expect(result.returnPercent).toBeNull();
|
||||
});
|
||||
it('keeps no-position and offsetting long/short holdings honest', () => {
|
||||
expect(instrumentPosition(portfolio([]), 'common', 'STOCK')).toMatchObject({
|
||||
quantity: '0',
|
||||
accounts: [],
|
||||
currentValue: null,
|
||||
returnPercent: null,
|
||||
});
|
||||
expect(
|
||||
instrumentPosition(portfolio([row(), row('-2')]), 'common', 'STOCK')
|
||||
.averagePurchasePrice,
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user