feat(tbank): add brokerage account summaries
This commit is contained in:
@@ -16,16 +16,24 @@ describe('BrokerageAccountsService', () => {
|
||||
const encryptionKey = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
||||
const prisma = { tbankConnection: { findUnique: jest.fn() } };
|
||||
const config = { getOrThrow: jest.fn(() => encryptionKey) };
|
||||
const tbank = { getOpenAccounts: jest.fn() };
|
||||
const tbank = {
|
||||
getOpenAccounts: jest.fn(),
|
||||
getPortfolio: jest.fn(),
|
||||
getPositions: jest.fn(),
|
||||
};
|
||||
const tokens = new TbankTokenService(config as never);
|
||||
const access = new TbankAccessService(
|
||||
prisma as never,
|
||||
tokens,
|
||||
tbank as never,
|
||||
);
|
||||
const service = new BrokerageAccountsService(access);
|
||||
const service = new BrokerageAccountsService(access, tbank as never);
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
tbank.getPortfolio.mockResolvedValue({ positions: [] });
|
||||
tbank.getPositions.mockResolvedValue({ money: [] });
|
||||
});
|
||||
|
||||
it('does not call T-Bank without a connection', async () => {
|
||||
prisma.tbankConnection.findUnique.mockResolvedValue(null);
|
||||
@@ -65,16 +73,102 @@ describe('BrokerageAccountsService', () => {
|
||||
type: 'BROKERAGE',
|
||||
name: 'Основной',
|
||||
openedOn: '2024-08-13',
|
||||
portfolioSummary: { totalsByCurrency: [] },
|
||||
},
|
||||
{
|
||||
id: 'iis',
|
||||
type: 'INDIVIDUAL_INVESTMENT',
|
||||
name: 'ИИС',
|
||||
openedOn: null,
|
||||
portfolioSummary: { totalsByCurrency: [] },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps upstream order and adds exact summaries without loading metadata', async () => {
|
||||
prisma.tbankConnection.findUnique.mockResolvedValue({
|
||||
encryptedToken: tokens.encrypt('token'),
|
||||
});
|
||||
tbank.getOpenAccounts.mockResolvedValue([
|
||||
{ id: 'first', type: 'ACCOUNT_TYPE_TINKOFF', name: 'Первый' },
|
||||
{ id: 'second', type: 'ACCOUNT_TYPE_TINKOFF_IIS', name: 'Второй' },
|
||||
]);
|
||||
tbank.getPortfolio
|
||||
.mockResolvedValueOnce({
|
||||
positions: [
|
||||
{
|
||||
quantity: { units: 2 },
|
||||
currentPrice: { currency: 'rub', units: 3 },
|
||||
},
|
||||
],
|
||||
})
|
||||
.mockResolvedValueOnce({ positions: [] });
|
||||
tbank.getPositions
|
||||
.mockResolvedValueOnce({ money: [{ currency: 'RUB', nano: 500000000 }] })
|
||||
.mockResolvedValueOnce({ money: [{ currency: 'USD', units: 4 }] });
|
||||
|
||||
await expect(service.list('user-1')).resolves.toEqual([
|
||||
{
|
||||
id: 'first',
|
||||
type: 'BROKERAGE',
|
||||
name: 'Первый',
|
||||
openedOn: null,
|
||||
portfolioSummary: {
|
||||
totalsByCurrency: [{ currency: 'RUB', value: '6.5' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'second',
|
||||
type: 'INDIVIDUAL_INVESTMENT',
|
||||
name: 'Второй',
|
||||
openedOn: null,
|
||||
portfolioSummary: {
|
||||
totalsByCurrency: [{ currency: 'USD', value: '4' }],
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(tbank.getPortfolio).toHaveBeenCalledWith('token', 'first');
|
||||
expect(tbank.getPositions).toHaveBeenCalledWith('token', 'second');
|
||||
});
|
||||
|
||||
it('localizes an unavailable summary without suppressing another account', async () => {
|
||||
prisma.tbankConnection.findUnique.mockResolvedValue({
|
||||
encryptedToken: tokens.encrypt('token'),
|
||||
});
|
||||
tbank.getOpenAccounts.mockResolvedValue([
|
||||
{ id: 'unavailable', type: 'ACCOUNT_TYPE_TINKOFF', name: 'Первый' },
|
||||
{ id: 'available', type: 'ACCOUNT_TYPE_TINKOFF', name: 'Второй' },
|
||||
]);
|
||||
tbank.getPortfolio
|
||||
.mockRejectedValueOnce(
|
||||
new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' }),
|
||||
)
|
||||
.mockResolvedValueOnce({ positions: [] });
|
||||
tbank.getPositions.mockResolvedValue({ money: [] });
|
||||
|
||||
await expect(service.list('user-1')).resolves.toMatchObject([
|
||||
{ id: 'unavailable', portfolioSummary: null },
|
||||
{ id: 'available', portfolioSummary: { totalsByCurrency: [] } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not mask an invalid token when the other summary input is unavailable', async () => {
|
||||
prisma.tbankConnection.findUnique.mockResolvedValue({
|
||||
encryptedToken: tokens.encrypt('token'),
|
||||
});
|
||||
tbank.getOpenAccounts.mockResolvedValue([
|
||||
{ id: 'account', type: 'ACCOUNT_TYPE_TINKOFF', name: 'Основной' },
|
||||
]);
|
||||
tbank.getPortfolio.mockRejectedValue(
|
||||
new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' }),
|
||||
);
|
||||
tbank.getPositions.mockRejectedValue(new Error('INVALID_TBANK_TOKEN'));
|
||||
|
||||
await expect(service.list('user-1')).rejects.toEqual(
|
||||
expect.any(BadRequestException),
|
||||
);
|
||||
});
|
||||
|
||||
it('retains the connection when T-Bank rejects its token', async () => {
|
||||
prisma.tbankConnection.findUnique.mockResolvedValue({
|
||||
encryptedToken: tokens.encrypt('token'),
|
||||
|
||||
Reference in New Issue
Block a user