105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
jest.mock('../../../prisma/prisma.service', () => ({
|
|
PrismaService: class {},
|
|
}));
|
|
import { investmentResourceMeta } from '../investment-resource.mapper';
|
|
import { BrokerageAccountsService } from './brokerage-accounts.service';
|
|
|
|
jest.mock('../portfolio/application/portfolio.service', () => ({
|
|
PortfolioService: class PortfolioService {},
|
|
}));
|
|
|
|
const meta = investmentResourceMeta({
|
|
resourceKey: 'membership',
|
|
dataRevision: '1',
|
|
availability: 'complete',
|
|
fetchedAt: new Date(),
|
|
freshForMs: 300_000,
|
|
work: null,
|
|
});
|
|
|
|
describe('saved brokerage account projection', () => {
|
|
const portfolio = { saved: jest.fn() };
|
|
const sync = { hourly: jest.fn().mockResolvedValue(null) };
|
|
const service = new BrokerageAccountsService(portfolio as never, sync as never);
|
|
|
|
it('distinguishes unknown membership from a confirmed empty list', async () => {
|
|
portfolio.saved.mockResolvedValueOnce({
|
|
membership: { ...meta, completeness: 'incomplete' },
|
|
accounts: [],
|
|
});
|
|
expect((await service.list('owner')).data).toBeNull();
|
|
portfolio.saved.mockResolvedValueOnce({ membership: meta, accounts: [] });
|
|
expect((await service.list('owner')).data).toEqual([]);
|
|
});
|
|
|
|
it('keeps exact saved totals and independent missing summaries', async () => {
|
|
portfolio.saved.mockResolvedValue({
|
|
membership: meta,
|
|
accounts: [
|
|
{
|
|
id: 'a',
|
|
name: 'A',
|
|
type: 'BROKERAGE',
|
|
openedOn: null,
|
|
meta,
|
|
snapshot: {
|
|
totalsByCurrency: [
|
|
{ currency: 'RUB', value: '100000000.000000001' },
|
|
],
|
|
positions: [{ type: 'STOCK' }, { type: 'BOND' }],
|
|
},
|
|
},
|
|
{
|
|
id: 'b',
|
|
name: 'B',
|
|
type: 'BROKERAGE',
|
|
openedOn: null,
|
|
meta: { ...meta, availability: 'missing' },
|
|
snapshot: null,
|
|
},
|
|
],
|
|
});
|
|
const response = await service.list('owner');
|
|
expect(response.data?.[0].portfolioSummary).toEqual({
|
|
totalsByCurrency: [{ currency: 'RUB', value: '100000000.000000001' }],
|
|
stockPositionCount: 1,
|
|
bondPositionCount: 1,
|
|
});
|
|
expect(response.data?.[1].portfolioSummary).toBeNull();
|
|
expect(response.meta.availability).toBe('available');
|
|
expect(response.meta.completeness).toBe('complete');
|
|
});
|
|
|
|
it('observes the latest hourly walk without starting another one', async () => {
|
|
portfolio.saved.mockResolvedValue({ membership: meta, accounts: [] });
|
|
sync.hourly.mockResolvedValue({
|
|
startedAt: new Date('2026-09-25T12:00:00Z'),
|
|
finishedAt: new Date('2026-09-25T12:01:00Z'),
|
|
state: 'partial',
|
|
sections: [
|
|
{
|
|
section: 'portfolio',
|
|
accountId: 'a',
|
|
errorCode: 'TBANK_UNAVAILABLE',
|
|
},
|
|
],
|
|
});
|
|
|
|
const response = await service.list('owner');
|
|
|
|
expect(response.sync).toEqual({
|
|
startedAt: '2026-09-25T12:00:00.000Z',
|
|
finishedAt: '2026-09-25T12:01:00.000Z',
|
|
state: 'partial',
|
|
problems: [
|
|
{
|
|
section: 'portfolio',
|
|
accountId: 'a',
|
|
errorCode: 'TBANK_UNAVAILABLE',
|
|
},
|
|
],
|
|
});
|
|
expect(sync.hourly).toHaveBeenCalledWith('owner');
|
|
});
|
|
});
|