188 lines
5.3 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|