test: align resource availability metadata
This commit was merged in pull request #50.
This commit is contained in:
@@ -136,11 +136,6 @@
|
|||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"src/modules/investments/portfolio/application/portfolio.service.ts": {
|
|
||||||
"sonarjs/no-nested-conditional": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"src/modules/investments/tbank-sync/tbank-screen.ts": {
|
"src/modules/investments/tbank-sync/tbank-screen.ts": {
|
||||||
"sonarjs/cognitive-complexity": {
|
"sonarjs/cognitive-complexity": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|||||||
@@ -119,7 +119,10 @@ export class IncomeAnalyticsService {
|
|||||||
...investmentResourceMeta({
|
...investmentResourceMeta({
|
||||||
resourceKey: `income/${accountId}/${plan.selected.from}/${plan.selected.to}`,
|
resourceKey: `income/${accountId}/${plan.selected.from}/${plan.selected.to}`,
|
||||||
dataRevision: current.meta.dataRevision,
|
dataRevision: current.meta.dataRevision,
|
||||||
availability: current.meta.availability,
|
availability:
|
||||||
|
current.items === null
|
||||||
|
? 'temporarily-unavailable'
|
||||||
|
: 'available',
|
||||||
fetchedAt: current.meta.lastSuccessfulAt
|
fetchedAt: current.meta.lastSuccessfulAt
|
||||||
? new Date(current.meta.lastSuccessfulAt)
|
? new Date(current.meta.lastSuccessfulAt)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -113,6 +113,11 @@ function availability(value: LegacyAvailability, work: Work) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
if (value === 'complete') return 'available' as const;
|
if (value === 'complete') return 'available' as const;
|
||||||
|
if (value === 'partial') {
|
||||||
|
return work?.state === 'running'
|
||||||
|
? ('initial-loading' as const)
|
||||||
|
: ('temporarily-unavailable' as const);
|
||||||
|
}
|
||||||
return work?.state === 'running'
|
return work?.state === 'running'
|
||||||
? ('initial-loading' as const)
|
? ('initial-loading' as const)
|
||||||
: ('temporarily-unavailable' as const);
|
: ('temporarily-unavailable' as const);
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ describe('persistent portfolio projection', () => {
|
|||||||
const partial = await service.read('user', 'all');
|
const partial = await service.read('user', 'all');
|
||||||
expect(partial.data).toBeNull();
|
expect(partial.data).toBeNull();
|
||||||
expect(partial.meta.availability).toBe('temporarily-unavailable');
|
expect(partial.meta.availability).toBe('temporarily-unavailable');
|
||||||
|
expect(partial.meta.completeness).toBe('incomplete');
|
||||||
const saved = await service.saved('user', 'all');
|
const saved = await service.saved('user', 'all');
|
||||||
expect(saved.accounts[0].snapshot?.totalsByCurrency).toEqual(
|
expect(saved.accounts[0].snapshot?.totalsByCurrency).toEqual(
|
||||||
snapshot.totalsByCurrency,
|
snapshot.totalsByCurrency,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { mapPortfolio } from '../infrastructure/tbank-portfolio.mapper';
|
|||||||
import { PortfolioDto } from '../presentation/portfolio.dto';
|
import { PortfolioDto } from '../presentation/portfolio.dto';
|
||||||
|
|
||||||
const MISSING_DATA_REVISION = 0;
|
const MISSING_DATA_REVISION = 0;
|
||||||
|
const NO_ACCOUNTS = 0;
|
||||||
|
|
||||||
const membershipFreshForMs = tbankSourceTtlMs.membership;
|
const membershipFreshForMs = tbankSourceTtlMs.membership;
|
||||||
|
|
||||||
@@ -113,7 +114,11 @@ export class PortfolioService {
|
|||||||
const { hourly, ...saved } = await this.saved(userId, accountId);
|
const { hourly, ...saved } = await this.saved(userId, accountId);
|
||||||
const missing =
|
const missing =
|
||||||
saved.membership.completeness === 'incomplete' ||
|
saved.membership.completeness === 'incomplete' ||
|
||||||
saved.accounts.some((account) => !account.snapshot);
|
(saved.accounts.length > NO_ACCOUNTS && saved.accounts.some((account) => !account.snapshot));
|
||||||
|
const available =
|
||||||
|
!missing &&
|
||||||
|
(saved.membership.availability === 'available' ||
|
||||||
|
saved.accounts.some((account) => account.snapshot));
|
||||||
const metadata = await this.metadata.savedMany(
|
const metadata = await this.metadata.savedMany(
|
||||||
saved.accounts.flatMap((account) => account.snapshot?.positions ?? []),
|
saved.accounts.flatMap((account) => account.snapshot?.positions ?? []),
|
||||||
);
|
);
|
||||||
@@ -162,6 +167,11 @@ export class PortfolioService {
|
|||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
);
|
);
|
||||||
|
let availability: 'available' | 'complete' | 'missing' | 'partial';
|
||||||
|
if (available) availability = 'available';
|
||||||
|
else if (!missing) availability = 'complete';
|
||||||
|
else if (portfolios.length > NO_ACCOUNTS) availability = 'partial';
|
||||||
|
else availability = 'missing';
|
||||||
const lastSuccessfulAt =
|
const lastSuccessfulAt =
|
||||||
// eslint-disable-next-line sonarjs/no-alphabetical-sort -- Code-unit ordering is intentional for canonical keys and ISO dates; locale collation would change persisted bindings.
|
// eslint-disable-next-line sonarjs/no-alphabetical-sort -- Code-unit ordering is intentional for canonical keys and ISO dates; locale collation would change persisted bindings.
|
||||||
portfolios.map((portfolio) => portfolio.lastSuccessfulAt).sort()[0] ??
|
portfolios.map((portfolio) => portfolio.lastSuccessfulAt).sort()[0] ??
|
||||||
@@ -205,11 +215,7 @@ export class PortfolioService {
|
|||||||
]),
|
]),
|
||||||
metadataDependencies.map((dependency) => dependency.dataRevision),
|
metadataDependencies.map((dependency) => dependency.dataRevision),
|
||||||
]),
|
]),
|
||||||
availability: missing
|
availability,
|
||||||
? portfolios.length
|
|
||||||
? 'partial'
|
|
||||||
: 'missing'
|
|
||||||
: 'complete',
|
|
||||||
fetchedAt: lastSuccessfulAt ? new Date(lastSuccessfulAt) : null,
|
fetchedAt: lastSuccessfulAt ? new Date(lastSuccessfulAt) : null,
|
||||||
freshForMs: portfolios.length
|
freshForMs: portfolios.length
|
||||||
? portfolioFreshForMs
|
? portfolioFreshForMs
|
||||||
|
|||||||
@@ -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 () => {
|
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 ' });
|
const cold = await service.read('owner', { ...query, query: ' sber ' });
|
||||||
expect(cold.data).toBeNull();
|
expect(cold.data).toBeNull();
|
||||||
expect(cold.meta.availability).toBe('missing');
|
expect(cold.meta.availability).toBe('initial-loading');
|
||||||
await settled();
|
await settled();
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
service = createService();
|
service = createService();
|
||||||
const decrypt = jest.spyOn(tokens, 'decrypt');
|
const decrypt = jest.spyOn(tokens, 'decrypt');
|
||||||
const warm = await service.read('other', query);
|
const warm = await service.read('other', query);
|
||||||
expect(warm.data).toEqual([instrument]);
|
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(warm.meta.sync.id).toBeNull();
|
||||||
expect(decrypt).not.toHaveBeenCalled();
|
expect(decrypt).not.toHaveBeenCalled();
|
||||||
expect(upstream.findShares).toHaveBeenCalledTimes(1);
|
expect(upstream.findShares).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ describe('local income completeness and exact money (SQLite)', () => {
|
|||||||
for (let index = 0; index < 2; index++) {
|
for (let index = 0; index < 2; index++) {
|
||||||
const result = await read();
|
const result = await read();
|
||||||
assert(result.data);
|
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([
|
expect(result.data.totalsByCurrency.map((row) => row.total)).toEqual([
|
||||||
{ currency: 'RUB', value: '15.000000002' },
|
{ currency: 'RUB', value: '15.000000002' },
|
||||||
{ currency: 'USD', value: '6' },
|
{ currency: 'USD', value: '6' },
|
||||||
@@ -146,10 +147,9 @@ describe('local income completeness and exact money (SQLite)', () => {
|
|||||||
'previous',
|
'previous',
|
||||||
'year',
|
'year',
|
||||||
]);
|
]);
|
||||||
expect(
|
const previous = result.meta.dependencies.find(({ role }) => role === 'previous');
|
||||||
result.meta.dependencies.find(({ role }) => role === 'previous')
|
expect(previous?.availability).toBe('temporarily-unavailable');
|
||||||
?.availability,
|
expect(previous?.completeness).toBe('incomplete');
|
||||||
).toBe('missing');
|
|
||||||
expect(result.data.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
|
expect(result.data.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,9 @@ describe('local income completeness and exact money (SQLite)', () => {
|
|||||||
where: { accountId: 'b' },
|
where: { accountId: 'b' },
|
||||||
});
|
});
|
||||||
expect((await read()).data).toBeNull();
|
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')).data).not.toBeNull();
|
||||||
expect((await read('a', 'from=2025-03-01&to=2025-03-31')).data).toBeNull();
|
expect((await read('a', 'from=2025-03-01&to=2025-03-31')).data).toBeNull();
|
||||||
await coverage(
|
await coverage(
|
||||||
@@ -241,7 +243,7 @@ describe('local income completeness and exact money (SQLite)', () => {
|
|||||||
await repo.fail(fence, false);
|
await repo.fail(fence, false);
|
||||||
const after = await read();
|
const after = await read();
|
||||||
expect(after.data).toEqual(before.data);
|
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();
|
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('retains current-month observed completeness and does not renew source timestamps', async () => {
|
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`,
|
from: `${today.slice(0, 7)}-01`,
|
||||||
to: today,
|
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.meta.coverage?.asOf).toBe(asOf.toISOString());
|
||||||
expect(result.data?.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
|
expect(result.data?.calculatedAt).toBe('2025-02-01T00:00:00.000Z');
|
||||||
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
|
|||||||
`limit=2${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`,
|
`limit=2${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`,
|
||||||
).expect(200);
|
).expect(200);
|
||||||
const result = response.body as BrokerageOperationsResourceDto;
|
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);
|
assert(result.data);
|
||||||
ids.push(
|
ids.push(
|
||||||
...result.data.items.map((item) => `${item.account?.id}/${item.id}`),
|
...result.data.items.map((item) => `${item.account?.id}/${item.id}`),
|
||||||
@@ -251,15 +252,18 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
|
|||||||
.set('Authorization', auth());
|
.set('Authorization', auth());
|
||||||
const first = (await page().expect(200))
|
const first = (await page().expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.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);
|
assert(first.data?.nextCursor);
|
||||||
const second = (await page(first.data.nextCursor).expect(200))
|
const second = (await page(first.data.nextCursor).expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.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([]);
|
expect(second.meta.coverage?.missingRanges).toEqual([]);
|
||||||
const observed = (await page(first.data.nextCursor, true).expect(200))
|
const observed = (await page(first.data.nextCursor, true).expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.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();
|
expect(upstream.getOperationsByCursor).not.toHaveBeenCalled();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -384,12 +388,14 @@ describe('persistent ledger HTTP and cursor (SQLite)', () => {
|
|||||||
'a',
|
'a',
|
||||||
).expect(200)
|
).expect(200)
|
||||||
).body as BrokerageOperationsResourceDto;
|
).body as BrokerageOperationsResourceDto;
|
||||||
expect(partial.meta.availability).toBe('partial');
|
expect(partial.meta.availability).toBe('initial-loading');
|
||||||
expect(partial.data?.hasNext).toBe(false);
|
expect(partial.meta.completeness).toBe('incomplete');
|
||||||
|
expect(partial.data).toBeNull();
|
||||||
expect(partial.meta.coverage?.missingRanges.length).toBeGreaterThan(0);
|
expect(partial.meta.coverage?.missingRanges.length).toBeGreaterThan(0);
|
||||||
const whole = (await get('sync=observe', 'a').expect(200))
|
const whole = (await get('sync=observe', 'a').expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.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(whole.data?.items).toEqual([]);
|
||||||
expect(upstream.getOperationsByCursor.mock.calls.length).toBeGreaterThan(0);
|
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))
|
const cold = (await get('', 'a').expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.body as BrokerageOperationsResourceDto;
|
||||||
expect(cold.data).toBeNull();
|
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);
|
await get('sync=observe', 'a').expect(200);
|
||||||
expect(upstream.getOperationsByCursor).toHaveBeenCalledTimes(1);
|
expect(upstream.getOperationsByCursor).toHaveBeenCalledTimes(1);
|
||||||
// eslint-disable-next-line sonarjs/no-extra-arguments -- The typed callback is assigned the promise resolver before invocation.
|
// 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))
|
const result = (await get('sync=observe', 'a').expect(200))
|
||||||
.body as BrokerageOperationsResourceDto;
|
.body as BrokerageOperationsResourceDto;
|
||||||
expect(result.data?.items).toEqual([]);
|
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);
|
await get('sync=observe', 'a').expect(200);
|
||||||
expect(upstream.getOperationsByCursor.mock.calls.length).toBeGreaterThanOrEqual(count);
|
expect(upstream.getOperationsByCursor.mock.calls.length).toBeGreaterThanOrEqual(count);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
const cold = await get('brokerage-accounts/unknown/portfolio?sync=observe').expect(200);
|
const cold = await get('brokerage-accounts/unknown/portfolio?sync=observe').expect(200);
|
||||||
expect((cold.body as PortfolioResourceDto).data).toBeNull();
|
expect((cold.body as PortfolioResourceDto).data).toBeNull();
|
||||||
expect((cold.body as PortfolioResourceDto).meta.availability).toBe(
|
expect((cold.body as PortfolioResourceDto).meta.availability).toBe(
|
||||||
'missing',
|
'temporarily-unavailable',
|
||||||
);
|
);
|
||||||
await get('brokerage-accounts?sync=observe').expect(200);
|
await get('brokerage-accounts?sync=observe').expect(200);
|
||||||
expect(upstream.getOpenAccounts).not.toHaveBeenCalled();
|
expect(upstream.getOpenAccounts).not.toHaveBeenCalled();
|
||||||
@@ -321,7 +321,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
'brokerage-accounts/account/portfolio?sync=observe',
|
'brokerage-accounts/account/portfolio?sync=observe',
|
||||||
).expect(200);
|
).expect(200);
|
||||||
expect((failed.body as PortfolioResourceDto).data).toEqual(previous);
|
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);
|
expect(upstream.getPortfolio).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
where: { id: 'abandoned-membership' },
|
where: { id: 'abandoned-membership' },
|
||||||
data: { state: 'complete' },
|
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 prisma.tbankPortfolio.count()).toBe(0);
|
||||||
expect((await get('brokerage-accounts?sync=observe')).body).toMatchObject({
|
expect((await get('brokerage-accounts?sync=observe')).body).toMatchObject({
|
||||||
data: [{ id: 'account', portfolioSummary: null }],
|
data: [{ id: 'account', portfolioSummary: null }],
|
||||||
@@ -423,13 +423,14 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
{ currency: 'RUB', value: '21.1' },
|
{ currency: 'RUB', value: '21.1' },
|
||||||
]);
|
]);
|
||||||
expect(summaries.data?.[1].portfolioSummary).toBeNull();
|
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(
|
expect(
|
||||||
(await get('brokerage-accounts/all/portfolio?sync=observe')).body,
|
(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(
|
expect(
|
||||||
(await get(`stocks/${uid}/position?sync=observe`).expect(200)).body,
|
(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);
|
expect(upstream.getPortfolio).toHaveBeenCalledTimes(2);
|
||||||
await prisma.tbankAccount.deleteMany({ where: { accountId: 'second' } });
|
await prisma.tbankAccount.deleteMany({ where: { accountId: 'second' } });
|
||||||
await prisma.tbankSyncWork.updateMany({
|
await prisma.tbankSyncWork.updateMany({
|
||||||
@@ -444,7 +445,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
);
|
);
|
||||||
expect(position.body).toMatchObject({
|
expect(position.body).toMatchObject({
|
||||||
data: { quantity: '2', accounts: [{ account: { id: 'account' } }] },
|
data: { quantity: '2', accounts: [{ account: { id: 'account' } }] },
|
||||||
meta: { availability: 'complete' },
|
meta: { availability: 'available', completeness: 'complete' },
|
||||||
});
|
});
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
expect(
|
expect(
|
||||||
@@ -495,7 +496,7 @@ describe('local accounts and portfolio HTTP (SQLite)', () => {
|
|||||||
const empty = await get('brokerage-accounts?sync=observe').expect(200);
|
const empty = await get('brokerage-accounts?sync=observe').expect(200);
|
||||||
expect((empty.body as BrokerageAccountsResourceDto).data).toEqual([]);
|
expect((empty.body as BrokerageAccountsResourceDto).data).toEqual([]);
|
||||||
expect((empty.body as BrokerageAccountsResourceDto).meta.availability).toBe(
|
expect((empty.body as BrokerageAccountsResourceDto).meta.availability).toBe(
|
||||||
'complete',
|
'available',
|
||||||
);
|
);
|
||||||
await put().expect(200);
|
await put().expect(200);
|
||||||
await seedMembership();
|
await seedMembership();
|
||||||
|
|||||||
Reference in New Issue
Block a user