feat: reconcile hourly investment resource states
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
jest.mock('../../prisma/prisma.service', () => ({
|
||||
PrismaService: class {},
|
||||
}));
|
||||
|
||||
import { investmentResourceMeta } from './investment-resource.mapper';
|
||||
import { hourlyAttempt } from './tbank-sync/tbank-sync.repository';
|
||||
|
||||
describe('investment resource source freshness', () => {
|
||||
afterEach(() => jest.useRealTimers());
|
||||
@@ -36,6 +41,105 @@ describe('investment resource source freshness', () => {
|
||||
}).freshness,
|
||||
).toBe('unknown');
|
||||
});
|
||||
|
||||
it('keeps confirmed data state independent from a failed hourly attempt', () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2026-09-25T11:30:00Z'));
|
||||
const meta = investmentResourceMeta({
|
||||
resourceKey: 'portfolio/a',
|
||||
dataRevision: '1',
|
||||
availability: 'complete',
|
||||
fetchedAt: new Date('2026-09-25T11:00:00Z'),
|
||||
freshForMs: 3_600_000,
|
||||
work: null,
|
||||
lastAttempt: {
|
||||
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(meta).toMatchObject({
|
||||
availability: 'available',
|
||||
completeness: 'complete',
|
||||
freshness: 'fresh',
|
||||
lastSuccessfulAt: '2026-09-25T11:00:00.000Z',
|
||||
lastAttempt: {
|
||||
state: 'partial',
|
||||
startedAt: '2026-09-25T12:00:00.000Z',
|
||||
finishedAt: '2026-09-25T12:01:00.000Z',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('projects only the matching hourly section into a resource attempt', () => {
|
||||
const attempt = hourlyAttempt(
|
||||
{
|
||||
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' },
|
||||
{ section: 'operations', accountId: 'a', errorCode: null },
|
||||
{ section: 'events', errorCode: 'CALENDAR_UNAVAILABLE' },
|
||||
],
|
||||
},
|
||||
'portfolio',
|
||||
'a',
|
||||
);
|
||||
|
||||
const meta = investmentResourceMeta({
|
||||
resourceKey: 'portfolio/a',
|
||||
dataRevision: '1',
|
||||
availability: 'complete',
|
||||
fetchedAt: new Date('2026-09-25T11:00:00Z'),
|
||||
freshForMs: 3_600_000,
|
||||
work: { id: 'legacy-work', state: 'failed', errorCode: 'LEGACY_FAILURE' },
|
||||
lastAttempt: attempt,
|
||||
});
|
||||
|
||||
expect(meta).toMatchObject({
|
||||
availability: 'available',
|
||||
completeness: 'complete',
|
||||
lastAttempt: {
|
||||
state: 'partial',
|
||||
startedAt: '2026-09-25T12:00:00.000Z',
|
||||
finishedAt: '2026-09-25T12:01:00.000Z',
|
||||
problems: [{ section: 'portfolio', accountId: 'a', errorCode: 'TBANK_UNAVAILABLE' }],
|
||||
},
|
||||
});
|
||||
expect(JSON.stringify(meta)).not.toContain('legacy-work');
|
||||
});
|
||||
|
||||
it('projects no confirmed result as incomplete without inventing a successful attempt', () => {
|
||||
expect(
|
||||
investmentResourceMeta({
|
||||
resourceKey: 'accounts',
|
||||
dataRevision: '0',
|
||||
availability: 'missing',
|
||||
fetchedAt: null,
|
||||
freshForMs: 3_600_000,
|
||||
work: { id: 'work', state: 'running', errorCode: null },
|
||||
}),
|
||||
).toMatchObject({
|
||||
availability: 'initial-loading',
|
||||
completeness: 'incomplete',
|
||||
freshness: 'unknown',
|
||||
lastSuccessfulAt: null,
|
||||
lastAttempt: {
|
||||
state: 'running',
|
||||
startedAt: null,
|
||||
finishedAt: null,
|
||||
problems: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dependent resource lifecycle', () => {
|
||||
@@ -60,8 +164,8 @@ describe('dependent resource lifecycle', () => {
|
||||
...input,
|
||||
dependencies: [failed, running],
|
||||
});
|
||||
expect(parent.sync).toEqual(running.sync);
|
||||
expect(parent.availability).toBe('complete');
|
||||
expect(parent.lastAttempt.state).toBe('none');
|
||||
expect(parent.availability).toBe('available');
|
||||
const terminal = investmentResourceMeta({
|
||||
...input,
|
||||
dependencies: [failed],
|
||||
|
||||
Reference in New Issue
Block a user