feat: reconcile hourly investment resource states
This commit is contained in:
@@ -3,12 +3,12 @@ import { Module } from '@nestjs/common';
|
||||
import { TbankModule } from '../../../integrations/tbank/tbank.module';
|
||||
import { AuthModule } from '../../auth/auth.module';
|
||||
import { PortfolioModule } from '../portfolio/portfolio.module';
|
||||
import { TbankSyncModule } from '../tbank-sync/tbank-sync.module';
|
||||
import { TbankSyncRepositoryModule } from '../tbank-sync/tbank-sync-repository.module';
|
||||
import { BrokerageAccountsController } from './brokerage-accounts.controller';
|
||||
import { BrokerageAccountsService } from './brokerage-accounts.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, TbankModule, PortfolioModule, TbankSyncModule],
|
||||
imports: [AuthModule, TbankModule, PortfolioModule, TbankSyncRepositoryModule],
|
||||
controllers: [BrokerageAccountsController],
|
||||
providers: [BrokerageAccountsService],
|
||||
exports: [BrokerageAccountsService],
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('saved brokerage account projection', () => {
|
||||
|
||||
it('distinguishes unknown membership from a confirmed empty list', async () => {
|
||||
portfolio.saved.mockResolvedValueOnce({
|
||||
membership: { ...meta, availability: 'missing' },
|
||||
membership: { ...meta, completeness: 'incomplete' },
|
||||
accounts: [],
|
||||
});
|
||||
expect((await service.list('owner')).data).toBeNull();
|
||||
@@ -66,6 +66,39 @@ describe('saved brokerage account projection', () => {
|
||||
bondPositionCount: 1,
|
||||
});
|
||||
expect(response.data?.[1].portfolioSummary).toBeNull();
|
||||
expect(response.meta.availability).toBe('partial');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ export class BrokerageAccountsService {
|
||||
async list(userId: string): Promise<BrokerageAccountsResourceDto> {
|
||||
const saved = await this.portfolio.saved(userId, 'all');
|
||||
const data: BrokerageAccountDto[] | null =
|
||||
saved.membership.availability === 'missing'
|
||||
saved.membership.completeness === 'incomplete'
|
||||
? null
|
||||
: saved.accounts.map((account) => ({
|
||||
id: account.id,
|
||||
@@ -55,16 +55,12 @@ export class BrokerageAccountsService {
|
||||
availability:
|
||||
data === null
|
||||
? 'missing'
|
||||
: saved.accounts.some((account) => !account.snapshot)
|
||||
? 'partial'
|
||||
: 'complete',
|
||||
: 'complete',
|
||||
fetchedAt: saved.membership.lastSuccessfulAt
|
||||
? new Date(saved.membership.lastSuccessfulAt)
|
||||
: null,
|
||||
freshForMs: 3_600_000,
|
||||
work: saved.membership.sync.id
|
||||
? { ...saved.membership.sync, id: saved.membership.sync.id }
|
||||
: null,
|
||||
work: null,
|
||||
dependencies: saved.accounts.map((account) => account.meta),
|
||||
}),
|
||||
sync: hourly
|
||||
@@ -72,7 +68,10 @@ export class BrokerageAccountsService {
|
||||
state: hourly.state,
|
||||
startedAt: hourly.startedAt.toISOString(),
|
||||
finishedAt: hourly.finishedAt?.toISOString() ?? null,
|
||||
sections: hourly.sections,
|
||||
problems: hourly.sections.map((section) => ({
|
||||
...section,
|
||||
accountId: section.accountId ?? null,
|
||||
})),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user