Files
backend/src/modules/investments/operations/domain/operations-sync-plan.spec.ts
T
ksv741 0057925888
Backend verify / commits (pull_request) Successful in 33s
Backend verify / security (pull_request) Failing after 12s
Backend verify / verify (pull_request) Skipped
feat: add hourly portfolio sync history
2026-10-09 09:04:41 +03:00

168 lines
5.5 KiB
TypeScript

import { confirmedOperationRanges, operationIntervals, planOperationRead } from './operations-sync-plan';
describe('confirmed operation range projection', () => {
it('clips overlaps in source order without merging and respects account and instrument scope', () => {
const day = (value: number) => new Date(Date.UTC(2026, 0, value));
const target = { accountId: 'a', from: day(2), to: day(5) };
const sources = [
{ ...target, from: day(3), to: day(6) },
{ ...target, from: day(1), to: day(4) },
{ ...target, instrumentUid: 'uid' },
{ ...target, accountId: 'other' },
{ ...target, from: day(5), to: day(6) },
{ ...target, from: day(1), to: day(2) },
];
const before = structuredClone(sources);
expect(confirmedOperationRanges([target], sources)).toEqual([
{ ...target, from: day(3) },
{ ...target, to: day(4) },
]);
expect(confirmedOperationRanges([{ ...target, instrumentUid: 'uid' }], sources)).toEqual([
{ ...target, from: day(3) },
{ ...target, to: day(4) },
target,
]);
expect(sources).toEqual(before);
});
});
describe('operations calendar windows', () => {
it('merges overlaps before splitting leap February and keeps precise Moscow-derived boundaries', () => {
const result = operationIntervals([
{
accountId: 'a',
from: new Date('2024-02-01T00:00:00+03:00'),
to: new Date('2024-03-01T00:00:00+03:00'),
},
{
accountId: 'a',
from: new Date('2024-02-10Z'),
to: new Date('2024-04-01Z'),
},
]);
expect(
result.map(({ from, to }) => [from.toISOString(), to.toISOString()]),
).toEqual([
['2024-01-31T21:00:00.000Z', '2024-02-01T00:00:00.000Z'],
['2024-02-01T00:00:00.000Z', '2024-03-01T00:00:00.000Z'],
['2024-03-01T00:00:00.000Z', '2024-04-01T00:00:00.000Z'],
]);
});
it('does not combine account or instrument predicates and rejects invalid ranges', () => {
const range = {
from: new Date('2026-12-15Z'),
to: new Date('2027-01-15Z'),
};
const result = operationIntervals([
{ accountId: 'a', ...range },
{ accountId: 'b', ...range },
{ accountId: 'a', instrumentUid: 'uid', ...range },
]);
expect(result).toHaveLength(6);
expect(() =>
operationIntervals([{ accountId: 'a', from: range.to, to: range.from }]),
).toThrow();
});
});
describe('operation completeness and freshness', () => {
const from = new Date('2026-09-01Z');
const asOf = new Date('2026-09-25T12:00:00Z');
const covered = { accountId: 'a', from, to: asOf, asOf, fetchedAt: asOf };
it.each([1, 3599, 3600])(
'preserves the observed complete end after %s seconds, separately from TTL',
(seconds) => {
const now = new Date(asOf.valueOf() + seconds * 1000);
const plan = planOperationRead(
[{ accountId: 'a', from, to: now }],
[covered],
now,
{ movingEnd: true },
);
expect(plan.intervals[0]).toMatchObject({
availability: 'complete',
asOf,
to: asOf,
stale: seconds >= 3600,
});
expect(plan.load).toEqual(
[],
);
},
);
it('preserves a cursor observed end after the clock and calendar advance', () => {
const cursorEnd = new Date(asOf.valueOf() + 1000);
const plan = planOperationRead(
[{ accountId: 'a', from, to: cursorEnd }],
[covered],
new Date('2026-10-01Z'),
{ movingEnd: true },
);
expect(plan.intervals[0]).toMatchObject({
availability: 'complete',
to: asOf,
asOf,
});
expect(plan.load).toEqual([]);
});
it('does not hide earlier holes or reuse an instrument subset as whole-account completeness', () => {
const now = new Date(asOf.valueOf() + 1000);
const partial = { ...covered, from: new Date('2026-09-10Z') };
const plan = planOperationRead(
[{ accountId: 'a', from, to: now }],
[partial],
now,
{ movingEnd: true },
);
expect(plan.intervals[0]).toMatchObject({
availability: 'partial',
stale: false,
});
expect(plan.load).toEqual([{ accountId: 'a', from, to: partial.from }]);
expect(
planOperationRead(
[{ accountId: 'a', from, to: now }],
[{ ...covered, instrumentUid: 'uid' }],
now,
).intervals[0].availability,
).toBe('missing');
});
it('loads a newly explicit tail and closes the previous month after rollover', () => {
const now = new Date(asOf.valueOf() + 1000);
expect(
planOperationRead([{ accountId: 'a', from, to: now }], [covered], now)
.load,
).toEqual([{ accountId: 'a', from: asOf, to: now }]);
const monthEnd = new Date('2026-10-01Z');
const rollover = new Date('2026-10-01T00:00:01Z');
const plan = planOperationRead(
[{ accountId: 'a', from, to: rollover }],
[covered],
rollover,
{ movingEnd: true },
);
expect(plan.load).toEqual([
{ accountId: 'a', from: asOf, to: monthEnd },
{ accountId: 'a', from: monthEnd, to: rollover },
]);
expect(
planOperationRead(
[{ accountId: 'a', from, to: monthEnd }],
[{ ...covered, to: monthEnd, asOf: monthEnd }],
rollover,
).load,
).toEqual([]);
});
it('force refresh reloads only the shown target even when coverage is fresh', () => {
const target = { accountId: 'a', from: new Date('2026-09-20Z'), to: asOf };
expect(
planOperationRead([target], [covered], asOf, { force: true }).load,
).toEqual([target]);
});
});