import { Test, TestingModule } from '@nestjs/testing'; import { SecuritiesService } from './securities.service'; import { MoexClientService } from '../moex-client/moex-client.service'; import { CacheService } from '../cache/cache.service'; import { SecurityType } from './dto/search-query.dto'; describe('SecuritiesService', () => { let service: SecuritiesService; let moexClient: Pick; let cache: Pick; beforeEach(async () => { moexClient = { searchSecurities: vi.fn(), }; cache = { getOrFetch: vi.fn(async (_keyPrefix, _keyParts, fetchFn) => ({ data: await fetchFn(), fromCache: false, cachedAt: '2026-06-15T00:00:00.000Z', })), }; const module: TestingModule = await Test.createTestingModule({ providers: [ SecuritiesService, { provide: MoexClientService, useValue: moexClient }, { provide: CacheService, useValue: cache }, ], }).compile(); service = module.get(SecuritiesService); }); it('returns supported securities only and normalizes SUR currency to RUB', async () => { vi.mocked(moexClient.searchSecurities).mockResolvedValue([ { secid: 'SBER', isin: 'RU0009029540', name: 'Сбербанк России ПАО ао', shortName: 'Сбербанк', latName: 'Sberbank', listLevel: 1, issueSize: 21586948000, faceValue: 3, faceUnit: 'SUR', issueDate: '2007-07-20', typeName: 'Акция обыкновенная', group: 'stock_shares', type: 'common_share', isQualifiedInvestors: false, morningSession: true, eveningSession: true, }, { secid: 'SU26238RMFS5', isin: 'RU000A1038V6', name: 'ОФЗ 26238', shortName: 'ОФЗ 26238', latName: null, listLevel: 1, issueSize: 100000000, faceValue: 1000, faceUnit: 'RUB', issueDate: '2021-06-23', typeName: 'Государственная облигация', group: 'stock_bonds', type: 'ofz_bond', isQualifiedInvestors: false, morningSession: false, eveningSession: false, }, { secid: 'SiM6', isin: '', name: 'USD/RUB Futures', shortName: 'SiM6', latName: null, listLevel: 0, issueSize: 0, faceValue: 0, faceUnit: '', issueDate: '', typeName: 'Фьючерс', group: 'futures', type: 'futures', isQualifiedInvestors: false, morningSession: false, eveningSession: true, }, ]); const results = await service.search('SbEr', SecurityType.ALL, 10); expect(results).toEqual([ { secid: 'SBER', isin: 'RU0009029540', shortName: 'Сбербанк', type: 'share', listLevel: 1, currency: 'RUB', price: null, }, { secid: 'SU26238RMFS5', isin: 'RU000A1038V6', shortName: 'ОФЗ 26238', type: 'bond', listLevel: 1, currency: 'RUB', price: null, }, ]); expect(cache.getOrFetch).toHaveBeenCalledWith( 'search', ['sber'], expect.any(Function), 'searchTtl', ); expect(moexClient.searchSecurities).toHaveBeenCalledWith('SbEr'); }); it('filters by type and applies limit without live MOEX dependency', async () => { vi.mocked(cache.getOrFetch).mockResolvedValue({ data: [ { secid: 'SBER', isin: 'RU0009029540', shortName: 'Сбербанк', type: 'share', listLevel: 1, currency: 'RUB', price: null, }, { secid: 'GAZP', isin: 'RU0007661625', shortName: 'Газпром', type: 'share', listLevel: 1, currency: 'RUB', price: null, }, { secid: 'SU26238RMFS5', isin: 'RU000A1038V6', shortName: 'ОФЗ 26238', type: 'bond', listLevel: 1, currency: 'RUB', price: null, }, ], fromCache: true, cachedAt: null, }); const results = await service.search('ru', SecurityType.SHARE, 1); expect(results).toEqual([ { secid: 'SBER', isin: 'RU0009029540', shortName: 'Сбербанк', type: 'share', listLevel: 1, currency: 'RUB', price: null, }, ]); expect(moexClient.searchSecurities).not.toHaveBeenCalled(); }); });