import { Test, TestingModule } from '@nestjs/testing'; import { EntityNotFoundException } from '../../common/exceptions/entity-not-found.exception'; import { SharesService } from './shares.service'; import { MoexSecuritiesClient } from '../moex-client/moex-securities.client'; import { MoexMarketDataClient } from '../moex-client/moex-market-data.client'; import { MoexDividendsClient } from '../moex-client/moex-dividends.client'; import { MoexHistoryClient } from '../moex-client/moex-history.client'; import { CacheService } from '../cache/cache.service'; describe('SharesService', () => { let service: SharesService; let moexSecurities: Pick; let moexMarketData: Pick; let cache: Pick; beforeEach(async () => { moexSecurities = { getSecurityDescription: vi.fn(), }; moexMarketData = { getShareMarketData: 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: [ SharesService, { provide: MoexSecuritiesClient, useValue: moexSecurities }, { provide: MoexMarketDataClient, useValue: moexMarketData }, { provide: MoexDividendsClient, useValue: { getDividends: vi.fn() } }, { provide: MoexHistoryClient, useValue: { getHistory: vi.fn() } }, { provide: CacheService, useValue: cache }, ], }).compile(); service = module.get(SharesService); }); it('returns normalized SBER share spec and market data without live MOEX dependency', async () => { vi.mocked(moexSecurities.getSecurityDescription).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, }); vi.mocked(moexMarketData.getShareMarketData).mockResolvedValue({ secid: 'SBER', boardid: 'TQBR', shortName: 'Сбербанк', bid: 320, offer: 321, open: 318, low: 317, high: 325, last: 323, lastChange: 4, lastChangePrcnt: 1.25, volume: 1500000, value: 480000000, waprice: 321, numtrades: 4200, issueCapitalization: 6900000000000, tradingStatus: 'T', updateTime: '18:45:00', }); const result = await service.getShare('SBER'); expect(moexSecurities.getSecurityDescription).toHaveBeenCalledWith('SBER'); expect(cache.getOrFetch).toHaveBeenCalledWith( 'marketdata', ['shares', 'SBER'], expect.any(Function), 'marketDataTtl', ); expect(moexMarketData.getShareMarketData).toHaveBeenCalledWith('SBER'); expect(result.data).toMatchObject({ secid: 'SBER', isin: 'RU0009029540', name: 'Сбербанк России ПАО ао', shortName: 'Сбербанк', latName: 'Sberbank', listLevel: 1, issueSize: 21586948000, faceValue: 3, faceUnit: 'RUB', type: 'common_share', marketData: { price: 323, change: 4, changePercent: 1.25, open: 318, high: 325, low: 317, volume: 1500000, value: 480000000, issueCapitalization: 6900000000000, }, }); expect(result.data.marketData.updatedAt).toMatch(/T18:45:00$/); }); it('throws EntityNotFoundException for non-share security', async () => { vi.mocked(moexSecurities.getSecurityDescription).mockResolvedValue({ 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, }); await expect(service.getShare('SU26238RMFS5')).rejects.toBeInstanceOf(EntityNotFoundException); expect(cache.getOrFetch).not.toHaveBeenCalled(); }); });