moex-vibe/apps/backend/src/modules/shares/shares.service.spec.ts
Sergey Krylov 75fead68b8 refactor: split MoexClientService into domain-specific clients
- MoexHttpClient: infrastructure (axios, rate limiter, circuit breaker)
- MoexSecuritiesClient: search and security descriptions
- MoexMarketDataClient: share/bond market data and batch queries
- MoexCandlesClient: candle data
- MoexHistoryClient: share/bond history
- MoexDividendsClient: dividend data
- Removed @Global() from MoexClientModule
- Updated all 7 consumers with explicit DI
- All 141 tests passing
2026-06-25 20:49:17 +03:00

145 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<MoexSecuritiesClient, 'getSecurityDescription'>;
let moexMarketData: Pick<MoexMarketDataClient, 'getShareMarketData'>;
let cache: Pick<CacheService, 'getOrFetch'>;
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>(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();
});
});