138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
NotFoundException,
|
|
ServiceUnavailableException,
|
|
} from '@nestjs/common';
|
|
|
|
jest.mock('../prisma/prisma.service', () => ({
|
|
PrismaService: class PrismaService {},
|
|
}));
|
|
|
|
import { BrokerageOperationsService } from './brokerage-operations.service';
|
|
import { TbankCrypto } from './tbank.crypto';
|
|
|
|
describe('BrokerageOperationsService', () => {
|
|
const key = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
|
const prisma = { tbankConnection: { findUnique: jest.fn() } };
|
|
const config = { getOrThrow: jest.fn(() => key) };
|
|
const tbank = {
|
|
getOpenAccounts: jest.fn(),
|
|
getOperationsByCursor: jest.fn(),
|
|
};
|
|
const requestContext = { set: jest.fn() };
|
|
const service = new BrokerageOperationsService(
|
|
prisma as never,
|
|
config as never,
|
|
tbank as never,
|
|
requestContext as never,
|
|
);
|
|
const crypto = TbankCrypto.fromBase64(key);
|
|
let operationsRequest: unknown;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
prisma.tbankConnection.findUnique.mockResolvedValue({
|
|
encryptedToken: crypto.encrypt('token'),
|
|
});
|
|
tbank.getOpenAccounts.mockResolvedValue([
|
|
{ id: 'account', type: 'ACCOUNT_TYPE_TINKOFF' },
|
|
]);
|
|
tbank.getOperationsByCursor.mockImplementation(
|
|
(_token: string, request: unknown) => {
|
|
operationsRequest = request;
|
|
return Promise.resolve({ items: [] });
|
|
},
|
|
);
|
|
(service as unknown as { pages: Map<string, unknown> }).pages.clear();
|
|
});
|
|
|
|
it('uses the current UTC year by default and asks only for executed operations', async () => {
|
|
await service.get('user', 'account', {});
|
|
|
|
const request = operationsRequest as {
|
|
state: string;
|
|
from: { seconds: string };
|
|
};
|
|
expect(request.state).toBe('OPERATION_STATE_EXECUTED');
|
|
expect(request.from.seconds).toBe(
|
|
String(Math.floor(Date.UTC(new Date().getUTCFullYear(), 0, 1) / 1000)),
|
|
);
|
|
});
|
|
|
|
it('uses inclusive date boundaries, filters categories, and preserves cursors', async () => {
|
|
tbank.getOperationsByCursor.mockResolvedValue({
|
|
hasNext: true,
|
|
nextCursor: 'next',
|
|
items: [
|
|
{ id: 'income', date: { seconds: '1' }, type: 'OPERATION_TYPE_COUPON' },
|
|
{
|
|
id: 'fee',
|
|
date: { seconds: '2' },
|
|
type: 'OPERATION_TYPE_BROKER_FEE',
|
|
},
|
|
],
|
|
});
|
|
|
|
await expect(
|
|
service.get('user', 'account', {
|
|
from: '2025-01-01',
|
|
to: '2025-01-31',
|
|
categories: ['income'],
|
|
cursor: 'previous',
|
|
limit: 10,
|
|
}),
|
|
).resolves.toMatchObject({
|
|
items: [{ id: 'income' }],
|
|
nextCursor: 'next',
|
|
hasNext: true,
|
|
});
|
|
expect(tbank.getOperationsByCursor).toHaveBeenCalledWith(
|
|
'token',
|
|
expect.objectContaining({
|
|
from: { seconds: '1735689600' },
|
|
to: { seconds: '1738367999', nanos: 999_999_999 },
|
|
cursor: 'previous',
|
|
}),
|
|
);
|
|
});
|
|
|
|
it('returns an empty page without calling T-Bank when the period starts in the future', async () => {
|
|
await expect(
|
|
service.get('user', 'account', { from: '2999-01-01T00:00:00.000Z' }),
|
|
).resolves.toMatchObject({
|
|
accountId: 'account',
|
|
items: [],
|
|
nextCursor: null,
|
|
hasNext: false,
|
|
});
|
|
expect(tbank.getOperationsByCursor).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects inaccessible accounts before reading operations', async () => {
|
|
tbank.getOpenAccounts.mockResolvedValue([]);
|
|
|
|
await expect(service.get('user', 'account', {})).rejects.toEqual(
|
|
expect.any(NotFoundException),
|
|
);
|
|
expect(tbank.getOperationsByCursor).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('retains existing T-Bank error semantics', async () => {
|
|
tbank.getOpenAccounts.mockRejectedValueOnce(
|
|
new Error('INVALID_TBANK_TOKEN'),
|
|
);
|
|
await expect(service.get('user', 'account', {})).rejects.toEqual(
|
|
expect.any(BadRequestException),
|
|
);
|
|
tbank.getOpenAccounts.mockResolvedValueOnce([
|
|
{ id: 'account', type: 'ACCOUNT_TYPE_TINKOFF' },
|
|
]);
|
|
tbank.getOperationsByCursor.mockRejectedValueOnce(
|
|
new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' }),
|
|
);
|
|
await expect(service.get('user', 'account', {})).rejects.toEqual(
|
|
expect.any(ServiceUnavailableException),
|
|
);
|
|
});
|
|
});
|