fix(tbank): isolate caches by connection generation
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
NotFoundException,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
@@ -25,12 +26,14 @@ describe('BrokerageOperationsService', () => {
|
||||
metadata as never,
|
||||
);
|
||||
let operationsRequest: unknown;
|
||||
let generation: string;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
access.withAccount.mockReset();
|
||||
tbank.getOperationsByCursor.mockReset();
|
||||
metadata.resolve.mockReset();
|
||||
generation = 'generation-1';
|
||||
access.withAccount.mockImplementation(
|
||||
(
|
||||
_userId: string,
|
||||
@@ -39,6 +42,7 @@ describe('BrokerageOperationsService', () => {
|
||||
) =>
|
||||
handler({
|
||||
token: 'token',
|
||||
generation,
|
||||
account: { id: 'account', type: 'ACCOUNT_TYPE_TINKOFF', name: '' },
|
||||
}),
|
||||
);
|
||||
@@ -254,6 +258,54 @@ describe('BrokerageOperationsService', () => {
|
||||
expect(tbank.getOperationsByCursor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not reuse a cached ledger page across connection generations', async () => {
|
||||
const query = { from: '2025-01-01', to: '2025-01-31' };
|
||||
|
||||
await service.get('user', 'account', query);
|
||||
await service.get('user', 'account', query);
|
||||
generation = 'generation-2';
|
||||
await service.get('user', 'account', query);
|
||||
|
||||
expect(tbank.getOperationsByCursor).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('does not reuse a cached ledger page after the connection is deleted', async () => {
|
||||
const query = { from: '2025-01-01', to: '2025-01-31' };
|
||||
await service.get('user', 'account', query);
|
||||
access.withAccount.mockRejectedValueOnce(
|
||||
new ConflictException({ code: 'TBANK_NOT_CONNECTED' }),
|
||||
);
|
||||
|
||||
await expect(service.get('user', 'account', query)).rejects.toEqual(
|
||||
expect.any(ConflictException),
|
||||
);
|
||||
expect(tbank.getOperationsByCursor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps a late previous-generation page unavailable to ledger and income collection', async () => {
|
||||
let finishPrevious: ((value: unknown) => void) | undefined;
|
||||
const previous = new Promise((resolve) => {
|
||||
finishPrevious = resolve;
|
||||
});
|
||||
tbank.getOperationsByCursor
|
||||
.mockReturnValueOnce(previous)
|
||||
.mockResolvedValueOnce({
|
||||
items: [operation('current')],
|
||||
});
|
||||
const range = { from: '2025-01-01', to: '2025-01-31' };
|
||||
|
||||
const previousRequest = service.collectRanges('user', 'account', [range]);
|
||||
generation = 'generation-2';
|
||||
const current = await service.collectRanges('user', 'account', [range]);
|
||||
finishPrevious?.({ items: [operation('previous')] });
|
||||
await previousRequest;
|
||||
const reused = await service.collectRanges('user', 'account', [range]);
|
||||
|
||||
expect(current.pages[0][0].items[0].id).toBe('current');
|
||||
expect(reused.pages[0][0].items[0].id).toBe('current');
|
||||
expect(tbank.getOperationsByCursor).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('collects every cursor page and rejects a malformed continuation cursor', async () => {
|
||||
tbank.getOperationsByCursor
|
||||
.mockResolvedValueOnce({ hasNext: true, nextCursor: 'next', items: [] })
|
||||
@@ -311,3 +363,11 @@ describe('BrokerageOperationsService', () => {
|
||||
expect(tbank.getOperationsByCursor).toHaveBeenCalledTimes(102);
|
||||
});
|
||||
});
|
||||
|
||||
function operation(id: string) {
|
||||
return {
|
||||
id,
|
||||
date: { seconds: '1' },
|
||||
type: 'OPERATION_TYPE_COUPON',
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user