test: align resource availability metadata
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 13s
Backend verify / verify (push) Failing after 6m21s

This commit was merged in pull request #50.
This commit is contained in:
2026-10-09 23:10:50 +03:00
committed by ksv741
parent 947ffefa9b
commit bc631e72de
9 changed files with 62 additions and 39 deletions
+3 -2
View File
@@ -85,14 +85,15 @@ describe('shared catalog discovery (SQLite)', () => {
it('persists normalized query identity and confirmed empty separately from unknown, across users and reopen', async () => {
const cold = await service.read('owner', { ...query, query: ' sber ' });
expect(cold.data).toBeNull();
expect(cold.meta.availability).toBe('missing');
expect(cold.meta.availability).toBe('initial-loading');
await settled();
await prisma.$disconnect();
service = createService();
const decrypt = jest.spyOn(tokens, 'decrypt');
const warm = await service.read('other', query);
expect(warm.data).toEqual([instrument]);
expect(warm.meta.availability).toBe('complete');
expect(warm.meta.availability).toBe('available');
expect(warm.meta.completeness).toBe('complete');
expect(warm.meta.sync.id).toBeNull();
expect(decrypt).not.toHaveBeenCalled();
expect(upstream.findShares).toHaveBeenCalledTimes(1);
+11 -8
View File
@@ -128,7 +128,8 @@ describe('local income completeness and exact money (SQLite)', () => {
for (let index = 0; index < 2; index++) {
const result = await read();
assert(result.data);
expect(result.meta.availability).toBe('complete');
expect(result.meta.availability).toBe('available');
expect(result.meta.completeness).toBe('complete');
expect(result.data.totalsByCurrency.map((row) => row.total)).toEqual([
{ currency: 'RUB', value: '15.000000002' },
{ currency: 'USD', value: '6' },
@@ -146,10 +147,9 @@ describe('local income completeness and exact money (SQLite)', () => {
'previous',
'year',
]);
expect(
result.meta.dependencies.find(({ role }) => role === 'previous')
?.availability,
).toBe('missing');
const previous = result.meta.dependencies.find(({ role }) => role === 'previous');
expect(previous?.availability).toBe('temporarily-unavailable');
expect(previous?.completeness).toBe('incomplete');
expect(result.data.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
await prisma.$disconnect();
}
@@ -161,7 +161,9 @@ describe('local income completeness and exact money (SQLite)', () => {
where: { accountId: 'b' },
});
expect((await read()).data).toBeNull();
expect((await read()).meta.availability).toBe('partial');
const incomplete = await read();
expect(incomplete.meta.availability).toBe('temporarily-unavailable');
expect(incomplete.meta.completeness).toBe('incomplete');
expect((await read('a')).data).not.toBeNull();
expect((await read('a', 'from=2025-03-01&to=2025-03-31')).data).toBeNull();
await coverage(
@@ -241,7 +243,7 @@ describe('local income completeness and exact money (SQLite)', () => {
await repo.fail(fence, false);
const after = await read();
expect(after.data).toEqual(before.data);
expect(after.meta.sync.state).toBe('failed');
expect(after.meta.lastAttempt.state).toBe('failed');
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
});
it('retains current-month observed completeness and does not renew source timestamps', async () => {
@@ -261,7 +263,8 @@ describe('local income completeness and exact money (SQLite)', () => {
from: `${today.slice(0, 7)}-01`,
to: today,
});
expect(result.meta.availability).toBe('complete');
expect(result.meta.availability).toBe('available');
expect(result.meta.completeness).toBe('complete');
expect(result.meta.coverage?.asOf).toBe(asOf.toISOString());
expect(result.data?.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
+17 -9
View File
@@ -118,7 +118,8 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
`limit=2${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`,
).expect(200);
const result = response.body as BrokerageOperationsResourceDto;
expect(result.meta.availability).toBe('complete');
expect(result.meta.availability).toBe('available');
expect(result.meta.completeness).toBe('complete');
assert(result.data);
ids.push(
...result.data.items.map((item) => `${item.account?.id}/${item.id}`),
@@ -251,15 +252,18 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
.set('Authorization', auth());
const first = (await page().expect(200))
.body as BrokerageOperationsResourceDto;
expect(first.meta.availability).toBe('complete');
expect(first.meta.availability).toBe('available');
expect(first.meta.completeness).toBe('complete');
assert(first.data?.nextCursor);
const second = (await page(first.data.nextCursor).expect(200))
.body as BrokerageOperationsResourceDto;
expect(second.meta.availability).toBe('complete');
expect(second.meta.availability).toBe('available');
expect(second.meta.completeness).toBe('complete');
expect(second.meta.coverage?.missingRanges).toEqual([]);
const observed = (await page(first.data.nextCursor, true).expect(200))
.body as BrokerageOperationsResourceDto;
expect(observed.meta.availability).toBe('complete');
expect(observed.meta.availability).toBe('available');
expect(observed.meta.completeness).toBe('complete');
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
},
);
@@ -384,12 +388,14 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
'a',
).expect(200)
).body as BrokerageOperationsResourceDto;
expect(partial.meta.availability).toBe('partial');
expect(partial.data?.hasNext).toBe(false);
expect(partial.meta.availability).toBe('initial-loading');
expect(partial.meta.completeness).toBe('incomplete');
expect(partial.data).toBeNull();
expect(partial.meta.coverage?.missingRanges.length).toBeGreaterThan(0);
const whole = (await get('sync=observe', 'a').expect(200))
.body as BrokerageOperationsResourceDto;
expect(whole.meta.availability).toBe('complete');
expect(whole.meta.availability).toBe('available');
expect(whole.meta.completeness).toBe('complete');
expect(whole.data?.items).toEqual([]);
expect(upstream.getOperationsByCursor.mock.calls.length).toBeGreaterThan(0);
});
@@ -419,7 +425,8 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
const cold = (await get('', 'a').expect(200))
.body as BrokerageOperationsResourceDto;
expect(cold.data).toBeNull();
expect(cold.meta.availability).toBe('missing');
expect(cold.meta.availability).toBe('initial-loading');
expect(cold.meta.completeness).toBe('incomplete');
await get('sync=observe', 'a').expect(200);
expect(upstream.getOperationsByCursor).toHaveBeenCalledTimes(1);
// eslint-disable-next-line sonarjs/no-extra-arguments -- The typed callback is assigned the promise resolver before invocation.
@@ -434,7 +441,8 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
const result = (await get('sync=observe', 'a').expect(200))
.body as BrokerageOperationsResourceDto;
expect(result.data?.items).toEqual([]);
expect(result.meta.availability).toBe('complete');
expect(result.meta.availability).toBe('available');
expect(result.meta.completeness).toBe('complete');
await get('sync=observe', 'a').expect(200);
expect(upstream.getOperationsByCursor.mock.calls.length).toBeGreaterThanOrEqual(count);
});
+9 -8
View File
@@ -151,7 +151,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
const cold = await get('brokerage-accounts/unknown/portfolio?sync=observe').expect(200);
expect((cold.body as PortfolioResourceDto).data).toBeNull();
expect((cold.body as PortfolioResourceDto).meta.availability).toBe(
'missing',
'temporarily-unavailable',
);
await get('brokerage-accounts?sync=observe').expect(200);
expect(upstream.getOpenAccounts).not.toHaveBeenCalled();
@@ -321,7 +321,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
'brokerage-accounts/account/portfolio?sync=observe',
).expect(200);
expect((failed.body as PortfolioResourceDto).data).toEqual(previous);
expect((failed.body as PortfolioResourceDto).meta.sync.state).toBe('idle');
expect((failed.body as PortfolioResourceDto).meta.lastAttempt.state).toBe('none');
expect(upstream.getPortfolio).toHaveBeenCalledTimes(1);
});
@@ -375,7 +375,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
where: { id: 'abandoned-membership' },
data: { state: 'complete' },
});
expect((await get('brokerage-accounts/account/portfolio?sync=observe').expect(200)).body).toMatchObject({ data: null, meta: { availability: 'missing' } });
expect((await get('brokerage-accounts/account/portfolio?sync=observe').expect(200)).body).toMatchObject({ data: null, meta: { availability: 'temporarily-unavailable', completeness: 'incomplete' } });
expect(await prisma.tbankPortfolio.count()).toBe(0);
expect((await get('brokerage-accounts?sync=observe')).body).toMatchObject({
data: [{ id: 'account', portfolioSummary: null }],
@@ -423,13 +423,14 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
{ currency: 'RUB', value: '21.1' },
]);
expect(summaries.data?.[1].portfolioSummary).toBeNull();
expect(summaries.meta.availability).toBe('partial');
expect(summaries.meta.availability).toBe('available');
expect(summaries.meta.completeness).toBe('complete');
expect(
(await get('brokerage-accounts/all/portfolio?sync=observe')).body,
).toMatchObject({ data: null, meta: { availability: 'partial' } });
).toMatchObject({ data: null, meta: { availability: 'temporarily-unavailable', completeness: 'incomplete' } });
expect(
(await get(`stocks/${uid}/position?sync=observe`).expect(200)).body,
).toMatchObject({ data: null, meta: { availability: 'partial' } });
).toMatchObject({ data: null, meta: { availability: 'temporarily-unavailable', completeness: 'incomplete' } });
expect(upstream.getPortfolio).toHaveBeenCalledTimes(2);
await prisma.tbankAccount.deleteMany({ where: { accountId: 'second' } });
await prisma.tbankSyncWork.updateMany({
@@ -444,7 +445,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
);
expect(position.body).toMatchObject({
data: { quantity: '2', accounts: [{ account: { id: 'account' } }] },
meta: { availability: 'complete' },
meta: { availability: 'available', completeness: 'complete' },
});
await prisma.$disconnect();
expect(
@@ -495,7 +496,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
const empty = await get('brokerage-accounts?sync=observe').expect(200);
expect((empty.body as BrokerageAccountsResourceDto).data).toEqual([]);
expect((empty.body as BrokerageAccountsResourceDto).meta.availability).toBe(
'complete',
'available',
);
await put().expect(200);
await seedMembership();