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'); }); });