codex/backend-architecture-refactor #48
@ -4,7 +4,7 @@ import { CacheService } from '../../cache/cache.service';
|
||||
|
||||
describe('BrokerAccountsService', () => {
|
||||
const client = {
|
||||
getServiceClient: vi.fn(),
|
||||
getUsersClient: vi.fn(),
|
||||
callUnary: vi.fn(),
|
||||
} as unknown as TBankClientService;
|
||||
const cache = {
|
||||
@ -23,7 +23,7 @@ describe('BrokerAccountsService', () => {
|
||||
cachedAt: '2026-06-16T02:30:00.000Z',
|
||||
}),
|
||||
);
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({ getAccounts: vi.fn() } as any);
|
||||
vi.mocked(client.getUsersClient).mockReturnValue({ getAccounts: vi.fn() } as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValue({
|
||||
accounts: [
|
||||
{ id: '1', type: 'ACCOUNT_TYPE_TINKOFF', name: 'Broker', status: 'ACCOUNT_STATUS_OPEN' },
|
||||
|
||||
@ -32,9 +32,9 @@ export class BrokerAccountsService {
|
||||
}
|
||||
|
||||
private async fetchAccounts(): Promise<BrokerAccount[]> {
|
||||
const usersClient = this.tbankClient.getServiceClient('UsersService') as any;
|
||||
const usersClient = this.tbankClient.getUsersClient();
|
||||
const response = await this.tbankClient.callUnary<
|
||||
Record<string, string>,
|
||||
{ status: string },
|
||||
TBankAccountsResponse
|
||||
>(
|
||||
'UsersService/GetAccounts',
|
||||
|
||||
@ -23,7 +23,7 @@ export class BrokerInstrumentsService {
|
||||
}
|
||||
|
||||
private async fetchByUid(instrumentUid: string): Promise<TBankInstrument | null> {
|
||||
const instrumentsClient = this.tbankClient.getServiceClient('InstrumentsService') as any;
|
||||
const instrumentsClient = this.tbankClient.getInstrumentsClient();
|
||||
const response = await this.tbankClient.callUnary<
|
||||
{ idType: string; id: string },
|
||||
TBankInstrumentResponse
|
||||
|
||||
@ -6,7 +6,7 @@ import { TBankClientService } from './tbank-client.service';
|
||||
|
||||
describe('BrokerOperationsService', () => {
|
||||
const accounts = { findById: vi.fn() } as unknown as BrokerAccountsService;
|
||||
const client = { getServiceClient: vi.fn(), callUnary: vi.fn() } as unknown as TBankClientService;
|
||||
const client = { getOperationsClient: vi.fn(), callUnary: vi.fn() } as unknown as TBankClientService;
|
||||
const cache = { getOrFetch: vi.fn() } as unknown as CacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -36,7 +36,7 @@ describe('BrokerOperationsService', () => {
|
||||
cachedAt: null,
|
||||
}),
|
||||
);
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({ getOperationsByCursor: vi.fn() } as any);
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({ getOperationsByCursor: vi.fn() } as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValue({
|
||||
hasNext: false,
|
||||
items: [{ cursor: 'c1', brokerAccountId: 'acc-1', type: 'OPERATION_TYPE_BUY' }],
|
||||
|
||||
@ -69,7 +69,7 @@ export class BrokerOperationsService {
|
||||
accountId: string,
|
||||
request: Record<string, unknown>,
|
||||
): Promise<BrokerOperationsPage> {
|
||||
const operationsClient = this.tbankClient.getServiceClient('OperationsService') as any;
|
||||
const operationsClient = this.tbankClient.getOperationsClient();
|
||||
const response = await this.tbankClient.callUnary<
|
||||
Record<string, unknown>,
|
||||
TBankOperationsByCursorResponse
|
||||
|
||||
@ -8,7 +8,7 @@ import { TBankClientService } from './tbank-client.service';
|
||||
describe('BrokerPortfolioService', () => {
|
||||
const accounts = { findById: vi.fn() } as unknown as BrokerAccountsService;
|
||||
const instruments = { findByInstrumentUid: vi.fn() } as unknown as BrokerInstrumentsService;
|
||||
const client = { getServiceClient: vi.fn(), callUnary: vi.fn() } as unknown as TBankClientService;
|
||||
const client = { getOperationsClient: vi.fn(), callUnary: vi.fn() } as unknown as TBankClientService;
|
||||
const cache = { getOrFetch: vi.fn() } as unknown as CacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -38,7 +38,7 @@ describe('BrokerPortfolioService', () => {
|
||||
cachedAt: null,
|
||||
}),
|
||||
);
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
getPositions: vi.fn(),
|
||||
} as any);
|
||||
@ -104,7 +104,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('returns first page of positions', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
@ -139,7 +139,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('paginates using cursor', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
@ -179,7 +179,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('returns last page with hasNext=false', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
@ -206,7 +206,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('caches positions with cursor/limit/type in key and tbankPositionsTtl', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
@ -229,7 +229,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('filters by instrument type and caches with type in key', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
@ -279,7 +279,7 @@ describe('BrokerPortfolioService', () => {
|
||||
it('returns empty items when type filter matches nothing', async () => {
|
||||
mockAccount();
|
||||
mockCache();
|
||||
vi.mocked(client.getServiceClient).mockReturnValue({
|
||||
vi.mocked(client.getOperationsClient).mockReturnValue({
|
||||
getPortfolio: vi.fn(),
|
||||
} as any);
|
||||
vi.mocked(client.callUnary).mockResolvedValueOnce({
|
||||
|
||||
@ -120,7 +120,7 @@ export class BrokerPortfolioService {
|
||||
'tbank:raw-portfolio',
|
||||
[accountId],
|
||||
async () => {
|
||||
const operationsClient = this.tbankClient.getServiceClient('OperationsService') as any;
|
||||
const operationsClient = this.tbankClient.getOperationsClient();
|
||||
return this.tbankClient.callUnary<
|
||||
{ accountId: string; currency: string },
|
||||
TBankPortfolioResponse
|
||||
@ -139,7 +139,7 @@ export class BrokerPortfolioService {
|
||||
}
|
||||
|
||||
private async fetchPositions(accountId: string): Promise<TBankPositionsResponse> {
|
||||
const operationsClient = this.tbankClient.getServiceClient('OperationsService') as any;
|
||||
const operationsClient = this.tbankClient.getOperationsClient();
|
||||
return this.tbankClient.callUnary<{ accountId: string }, TBankPositionsResponse>(
|
||||
'OperationsService/GetPositions',
|
||||
operationsClient.getPositions.bind(operationsClient),
|
||||
|
||||
@ -44,6 +44,16 @@ describe('TBankClientService', () => {
|
||||
expect(() => service.getServiceClient('UsersService')).not.toThrow();
|
||||
});
|
||||
|
||||
it('exposes typed service-client facades for broker services', () => {
|
||||
const service = new TBankClientService(config);
|
||||
|
||||
expect(service.getUsersClient()).toHaveProperty('getAccounts');
|
||||
expect(service.getOperationsClient()).toHaveProperty('getPortfolio');
|
||||
expect(service.getOperationsClient()).toHaveProperty('getPositions');
|
||||
expect(service.getOperationsClient()).toHaveProperty('getOperationsByCursor');
|
||||
expect(service.getInstrumentsClient()).toHaveProperty('getInstrumentBy');
|
||||
});
|
||||
|
||||
it('creates grpc SSL credentials with configured custom CA certificate', () => {
|
||||
const caPath = join(mkdtempSync(join(tmpdir(), 'tbank-ca-')), 'root.pem');
|
||||
writeFileSync(caPath, '-----BEGIN CERTIFICATE-----\ntest-ca\n-----END CERTIFICATE-----\n');
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { TBankNotConfiguredException, TBankApiException } from '../../../common/exceptions/tbank-api.exception';
|
||||
import type {
|
||||
TBankAccountsResponse,
|
||||
TBankInstrumentResponse,
|
||||
TBankOperationsByCursorResponse,
|
||||
TBankPortfolioResponse,
|
||||
TBankPositionsResponse,
|
||||
} from '../types/tbank-proto.types';
|
||||
import {
|
||||
CallOptions,
|
||||
ChannelCredentials,
|
||||
@ -26,6 +33,25 @@ type GrpcUnary<TRequest, TResponse> = (
|
||||
|
||||
type GrpcServiceConstructor = new (address: string, credentials: ChannelCredentials) => Client;
|
||||
|
||||
type TBankAccountsRequest = { status: string };
|
||||
type TBankPortfolioRequest = { accountId: string; currency: string };
|
||||
type TBankPositionsRequest = { accountId: string };
|
||||
type TBankInstrumentRequest = { idType: string; id: string };
|
||||
|
||||
export type TBankUsersClient = Client & {
|
||||
getAccounts: GrpcUnary<TBankAccountsRequest, TBankAccountsResponse>;
|
||||
};
|
||||
|
||||
export type TBankOperationsClient = Client & {
|
||||
getPortfolio: GrpcUnary<TBankPortfolioRequest, TBankPortfolioResponse>;
|
||||
getPositions: GrpcUnary<TBankPositionsRequest, TBankPositionsResponse>;
|
||||
getOperationsByCursor: GrpcUnary<Record<string, unknown>, TBankOperationsByCursorResponse>;
|
||||
};
|
||||
|
||||
export type TBankInstrumentsClient = Client & {
|
||||
getInstrumentBy: GrpcUnary<TBankInstrumentRequest, TBankInstrumentResponse>;
|
||||
};
|
||||
|
||||
type QueueName = 'operations' | 'instruments' | 'users';
|
||||
|
||||
@Injectable()
|
||||
@ -120,6 +146,18 @@ export class TBankClientService {
|
||||
return client;
|
||||
}
|
||||
|
||||
getUsersClient(): TBankUsersClient {
|
||||
return this.getServiceClient('UsersService') as TBankUsersClient;
|
||||
}
|
||||
|
||||
getOperationsClient(): TBankOperationsClient {
|
||||
return this.getServiceClient('OperationsService') as TBankOperationsClient;
|
||||
}
|
||||
|
||||
getInstrumentsClient(): TBankInstrumentsClient {
|
||||
return this.getServiceClient('InstrumentsService') as TBankInstrumentsClient;
|
||||
}
|
||||
|
||||
async callUnary<TRequest, TResponse>(
|
||||
label: string,
|
||||
method: GrpcUnary<TRequest, TResponse>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user