import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { SharesService } from './shares.service'; import { MoexClientService } from '../moex-client/moex-client.service'; import { CacheService } from '../cache/cache.service'; describe('SharesService', () => { let service: SharesService; let moexClient: Pick; let cache: Pick; beforeEach(async () => { moexClient = { getSecurityDescription: vi.fn(), 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: MoexClientService, useValue: moexClient }, { 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(moexClient.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(moexClient.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(moexClient.getSecurityDescription).toHaveBeenCalledWith('SBER'); expect(cache.getOrFetch).toHaveBeenCalledWith( 'marketdata', ['shares', 'SBER'], expect.any(Function), 'marketDataTtl', ); expect(moexClient.getShareMarketData).toHaveBeenCalledWith('SBER'); expect(result).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.marketData.updatedAt).toMatch(/T18:45:00$/); }); it('throws NotFoundException for non-share security', async () => { vi.mocked(moexClient.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(NotFoundException); expect(cache.getOrFetch).not.toHaveBeenCalled(); }); });