Files
backend/src/modules/investments/investment-sync-plan.spec.ts
T

188 lines
5.3 KiB
TypeScript

import {
accountHistory,
ledgerRange,
planInvestmentScreen,
} from './investment-sync-plan';
describe('screen dependency and history planning', () => {
const now = new Date('2026-09-25T12:00:00Z');
const context = {
membershipKnown: true,
accounts: [{ accountId: 'a', openedOn: '2020-01-01' }],
instruments: [
{
uid: 'uid',
type: 'STOCK' as const,
historyFrom: new Date('2020-01-01Z'),
},
],
};
it('prioritizes discovery, selected/comparisons, then history; Moscow dates are converted once', () => {
const plan = planInvestmentScreen(
{
screen: 'income',
accountId: 'a',
from: '2024-02-01',
to: '2024-02-29',
},
context,
now,
);
expect(plan.sources[0].kind).toBe('membership');
const selected = plan.sources.filter(
(source) => source.kind === 'operations' && source.priority === 'screen',
);
expect(
selected.some(
(source) => source.from?.toISOString() === '2024-01-31T21:00:00.000Z',
),
).toBe(true);
expect(
selected.some(
(source) => source.to?.toISOString() === '2024-02-29T21:00:00.000Z',
),
).toBe(true);
expect(plan.unavailable).toEqual([
{ resourceKey: 'income/yearAgo', reason: 'calendar_or_account_boundary' }, // gitleaks:allow public resource identifier
]);
const firstHistory = plan.sources.findIndex(
(source) => source.priority === 'history',
);
expect(
plan.sources
.slice(firstHistory)
.every((source) => source.priority === 'history'),
).toBe(true);
});
it('force contains only shown dependencies and retains instrument reconciliation predicates', () => {
const plan = planInvestmentScreen(
{
screen: 'stock',
instrumentUid: 'uid',
from: '2026-01-01',
to: '2026-02-01',
candlePeriod: 'year',
},
context,
now,
true,
);
expect(plan.sources.map((source) => source.kind)).toEqual(
expect.arrayContaining([
'membership',
'portfolio',
'operations',
'metadata',
'quote',
'company',
'calendar',
'candles',
]),
);
expect(plan.sources.every((source) => source.priority !== 'history')).toBe(
true,
);
expect(
plan.sources
.filter((source) => source.kind === 'operations')
.every((source) => source.instrumentUid === 'uid'),
).toBe(true);
const accounts = planInvestmentScreen(
{ screen: 'accounts' },
context,
now,
true,
);
expect(
accounts.sources.some((source) => source.kind === 'operations'),
).toBe(false);
});
it('never invents an account or candle history boundary; discovery gates unknown membership', () => {
const unknown = {
...context,
accounts: [{ accountId: 'a', openedOn: null }],
instruments: [],
};
expect(accountHistory(unknown.accounts, now)[0].from.toISOString()).toBe(
'1970-01-01T00:00:00.000Z',
);
expect(
ledgerRange(
undefined,
undefined,
'uid',
unknown.accounts,
now,
).from.toISOString(),
).toBe('1970-01-01T00:00:00.000Z');
const plan = planInvestmentScreen(
{ screen: 'bond', instrumentUid: 'uid', candlePeriod: 'all' },
unknown,
now,
);
expect(plan.sources.some((source) => source.kind === 'candles')).toBe(
false,
);
expect(plan.unavailable[0].reason).toBe('history_start_unavailable');
expect(
planInvestmentScreen(
{ screen: 'account' },
{ ...context, membershipKnown: false },
now,
).sources,
).toEqual([{ kind: 'membership', priority: 'discovery' }]);
});
it('keeps UTC event days separate from Moscow instrument calendar days at year rollover', () => {
const rollover = new Date('2026-12-31T22:00:00Z');
const events = planInvestmentScreen(
{ screen: 'events' },
context,
rollover,
true,
);
expect(
events.sources.find((source) => source.kind === 'calendar')?.from,
).toEqual(new Date('2026-12-31Z'));
const stock = planInvestmentScreen(
{ screen: 'stock', instrumentUid: 'uid' },
context,
rollover,
true,
);
expect(
stock.sources.find((source) => source.kind === 'calendar'),
).toMatchObject({
from: new Date('2022-01-01Z'),
to: new Date('2028-01-01Z'),
});
expect(rollover.toISOString()).toBe('2026-12-31T22:00:00.000Z');
});
it('keeps search queries separate and events scoped to their inclusive period', () => {
const search = planInvestmentScreen(
{ screen: 'stocks', query: ' sber ' },
context,
now,
true,
);
expect(
search.sources.find((source) => source.kind === 'catalog')?.query,
).toBe('SBER');
const events = planInvestmentScreen(
{ screen: 'events', from: '2026-10-01', to: '2026-10-31' },
context,
now,
true,
);
expect(
events.sources.find((source) => source.kind === 'calendar'),
).toMatchObject({
from: new Date('2026-10-01Z'),
to: new Date('2026-10-31Z'),
});
expect(events.sources.some((source) => source.kind === 'operations')).toBe(
false,
);
});
});