refactor: retire legacy tbank-sync status and sync=observe (WealthLedger/backend#196)
This commit is contained in:
@@ -34,12 +34,9 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
const get = (
|
||||
account = 'all',
|
||||
period = 'from=2025-01-01&to=2025-01-31',
|
||||
observe = true,
|
||||
) =>
|
||||
request(app.getHttpServer())
|
||||
.get(
|
||||
`/api/v1/brokerage-accounts/${account}/income-analytics?${period}${observe ? '&sync=observe' : ''}`,
|
||||
)
|
||||
.get(`/api/v1/brokerage-accounts/${account}/income-analytics?${period}`)
|
||||
.set('Authorization', authorization)
|
||||
.timeout(2000);
|
||||
const read = async (account = 'all', period?: string) =>
|
||||
@@ -127,7 +124,7 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
it('returns exact selected sums; reopen and observe need no RPC', async () => {
|
||||
it('returns exact selected sums and reuses covered reads without RPC', async () => {
|
||||
for (let index = 0; index < 2; index++) {
|
||||
const result = await read();
|
||||
assert(result.data);
|
||||
@@ -210,7 +207,9 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
expect(
|
||||
(await read('a', 'from=2025-03-01&to=2025-03-31')).data?.totalsByCurrency,
|
||||
).toEqual([]);
|
||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||
expect(upstream.getOperationsByCursor).toHaveBeenCalledWith(
|
||||
'synthetic', expect.objectContaining({ accountId: 'b' }),
|
||||
);
|
||||
});
|
||||
it('counts corrected identities once for the selected period', async () => {
|
||||
await prisma.tbankOperation.updateMany({
|
||||
@@ -274,7 +273,7 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
expect(after.meta.lastAttempt.state).toBe('none');
|
||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||
});
|
||||
it('retains current-month observed completeness and does not renew source timestamps', async () => {
|
||||
it('retains current-month completeness and does not renew source timestamps', async () => {
|
||||
const now = new Date();
|
||||
const today = moscowDate(now);
|
||||
const start = new Date(`${today.slice(0, 7)}-01T00:00:00+03:00`);
|
||||
@@ -283,7 +282,7 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
await coverage(accountId, start, asOf);
|
||||
}
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/v1/brokerage-accounts/all/income-summary?sync=observe')
|
||||
.get('/api/v1/brokerage-accounts/all/income-summary')
|
||||
.set('Authorization', authorization)
|
||||
.expect(200);
|
||||
const result = response.body as IncomeSummaryResourceDto;
|
||||
@@ -302,58 +301,32 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
where: { userId: 'income-owner' },
|
||||
data: { membershipAt: new Date('2020-01-01Z') },
|
||||
});
|
||||
const response = await get('a', undefined, false).expect(200);
|
||||
const response = await get('a').expect(200);
|
||||
const result = response.body as IncomeAnalyticsResourceDto;
|
||||
expect(result.data).not.toBeNull();
|
||||
expect(result.meta.completeness).toBe('complete');
|
||||
expect(upstream.getOpenAccounts).not.toHaveBeenCalled();
|
||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||
});
|
||||
it('cold income imports only the missing requested interval', async () => {
|
||||
it('ignores retired sync=observe on income reads and keeps the cold response incomplete', async () => {
|
||||
await prisma.tbankConnection.update({
|
||||
where: { userId: 'income-owner' },
|
||||
data: { membershipAt: new Date('2020-01-01Z') },
|
||||
});
|
||||
let release: () => void = () => {
|
||||
throw new Error('Upstream not initialized');
|
||||
};
|
||||
const blocked = new Promise<{ hasNext: boolean; items: never[] }>((resolve) => {
|
||||
release = () => resolve({ hasNext: false, items: [] });
|
||||
});
|
||||
upstream.getOperationsByCursor.mockReturnValue(blocked);
|
||||
const first = await get('a', 'from=2025-02-01&to=2025-02-28', false).expect(200);
|
||||
const cold = first.body as IncomeAnalyticsResourceDto;
|
||||
release();
|
||||
expect(cold.data).toBeNull();
|
||||
expect(cold.meta.completeness).toBe('incomplete');
|
||||
let result: IncomeAnalyticsResourceDto = cold;
|
||||
for (let attempt = 0; attempt < 100; attempt++) {
|
||||
const response = await get('a', 'from=2025-02-01&to=2025-02-28').expect(200);
|
||||
result = response.body as IncomeAnalyticsResourceDto;
|
||||
if (result.meta.completeness === 'complete') break;
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
expect(result.data).not.toBeNull();
|
||||
expect(result.meta.completeness).toBe('complete');
|
||||
const response = await request(app.getHttpServer())
|
||||
.get('/api/v1/brokerage-accounts/a/income-analytics?from=2025-02-01&to=2025-02-28&sync=observe')
|
||||
.set('Authorization', authorization)
|
||||
.expect(200);
|
||||
const result = response.body as IncomeAnalyticsResourceDto;
|
||||
expect(result.data).toBeNull();
|
||||
expect(result.meta.completeness).toBe('incomplete');
|
||||
expect(upstream.getOpenAccounts).not.toHaveBeenCalled();
|
||||
expect(upstream.getOperationsByCursor.mock.calls).toEqual([
|
||||
['synthetic', expect.objectContaining({
|
||||
accountId: 'a',
|
||||
from: { seconds: '1738357199' },
|
||||
to: { seconds: '1738368001' },
|
||||
})],
|
||||
['synthetic', expect.objectContaining({
|
||||
accountId: 'a',
|
||||
from: { seconds: '1738367999' },
|
||||
to: { seconds: '1740776401' },
|
||||
})],
|
||||
]);
|
||||
});
|
||||
it('returns while a cold upstream is blocked and validates access/period before scheduling', async () => {
|
||||
await get('absent', undefined, false).expect(404);
|
||||
await get('a', 'from=2025-02-31&to=2025-03-01', false).expect(400);
|
||||
await get('absent').expect(404);
|
||||
await get('a', 'from=2025-02-31&to=2025-03-01').expect(400);
|
||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||
const response = await get('all', undefined, false).expect(200);
|
||||
const response = await get('all').expect(200);
|
||||
expect((response.body as IncomeAnalyticsResourceDto).data).not.toBeNull();
|
||||
});
|
||||
it('plans only the selected period for a leap day without scheduling it', async () => {
|
||||
@@ -361,7 +334,7 @@ describe('local income completeness and exact money (SQLite)', () => {
|
||||
expect(result.meta.dependencies.map(({ role }) => role)).toEqual([
|
||||
'selected',
|
||||
]);
|
||||
expect(result.meta.dependencies[0].availability).toBe('temporarily-unavailable');
|
||||
expect(result.meta.dependencies[0].availability).toBe('initial-loading');
|
||||
expect(result.data).toBeNull();
|
||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user