refactor: inject TbankAccessService directly into stock screener (#151) #153

Closed
codex-bot wants to merge 1 commits from refactor/151-screener-validation-di into develop
4 changed files with 8 additions and 9 deletions
@@ -1,6 +1,5 @@
import { Module } from '@nestjs/common';
import { TbankAccessService } from '../../../integrations/tbank/tbank-access.service';
import { TbankModule } from '../../../integrations/tbank/tbank.module';
import { AuthModule } from '../../auth/auth.module';
import { PortfolioModule } from '../portfolio/portfolio.module';
@@ -21,7 +20,6 @@ import { StockController } from './stocks/presentation/stock.controller';
controllers: [StockController, BondController],
exports: [StockIncomeService, BondIncomeService],
providers: [
{ provide: 'TBANK_ACCESS', useExisting: TbankAccessService },
BondIncomeService,
BondMarketService,
GetStockCompanyUseCase,
@@ -1,3 +1,7 @@
jest.mock('../../../../../integrations/tbank/tbank.api', () => ({
TbankAccessService: class {},
}));
import { NotFoundException, ServiceUnavailableException } from '@nestjs/common';
import type { TbankShare } from '../../../../../integrations/tbank/tbank.api';
@@ -36,7 +40,7 @@ describe('StockScreenerService', () => {
client.getAllShares.mockResolvedValue(
Array.from({ length: pageSize + 1 }, (_, index) => share(index)),
);
service = new StockScreenerService(access, client as never, logger as never);
service = new StockScreenerService(access as never, client as never, logger as never);
});
it('stores the full selected catalog and returns stable non-overlapping pages', async () => {
@@ -1,6 +1,5 @@
import {
BadRequestException,
Inject,
Injectable,
NotFoundException,
ServiceUnavailableException,
@@ -10,6 +9,7 @@ import { randomUUID } from 'node:crypto';
import type { TbankShare } from '../../../../../integrations/tbank/tbank-market.types';
import { StructuredLogger } from '../../../../../common/logging/structured-logger.service';
import { TbankAccessService } from '../../../../../integrations/tbank/tbank.api';
import { TbankClient } from '../../../../../integrations/tbank/tbank.client';
import {
selectStockScreenerInstruments,
@@ -47,17 +47,13 @@ type StockScreenerPage = {
searchId: string;
};
type Snapshot = { asOf: string; expiresAt: number; items: StockScreenerItem[]; revision: string; userId: string };
type TbankAccess = {
local(userId: string): Promise<{ revision: string }>;
online<T>(userId: string, run: (connection: { revision: string; token: string }) => Promise<T>): Promise<T>;
};
@Injectable()
export class StockScreenerService {
private readonly snapshots = new Map<string, Snapshot>();
constructor(
@Inject('TBANK_ACCESS') private readonly access: TbankAccess,
private readonly access: TbankAccessService,
private readonly client: TbankClient,
private readonly logger: StructuredLogger,
) {}
@@ -91,6 +91,7 @@ export class StockController {
@Body(new ValidationPipe({ forbidNonWhitelisted: true, forbidUnknownValues: true, whitelist: true }))
criteria: StockScreenerCriteriaDto,
) {
// The global whitelist pipe strips unknown keys before this param pipe runs, so only the raw body can reject them.
const allowed = new Set(['countryOfRisk', 'currency', 'maxLot', 'query', 'realExchange', 'sector', 'shareType']);
if (Object.keys(request.body as object).some((key) => !allowed.has(key))) {
throw new BadRequestException();