Files
backend/src/modules/investments/instruments/application/instrument-position.spec.ts
T
codex-bot 17432dfee3
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m4s
Backend verify / verify (push) Failing after 7m15s
feat(portfolio): store and publish instrument sector and issuer (WealthLedger/main#48) (#148)
2026-11-01 19:13:32 +03:00

98 lines
2.8 KiB
TypeScript

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,
sector: null,
issuer: 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();
});
});