moex-vibe/apps/backend/src/modules/candles/candles.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

116 lines
2.9 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { CandlesService } from './candles.service';
import { MoexCandlesClient } from '../moex-client/moex-candles.client';
import { CacheService } from '../cache/cache.service';
import { CandleInterval } from './dto/candles-query.dto';
describe('CandlesService', () => {
let service: CandlesService;
let moexCandles: Pick<MoexCandlesClient, 'getCandles'>;
let cache: Pick<CacheService, 'getOrFetch'>;
beforeEach(async () => {
moexCandles = {
getCandles: 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: [
CandlesService,
{ provide: MoexCandlesClient, useValue: moexCandles },
{ provide: CacheService, useValue: cache },
],
}).compile();
service = module.get<CandlesService>(CandlesService);
});
it('uses MOEX interval 24 for daily share candles and maps output envelope', async () => {
vi.mocked(moexCandles.getCandles).mockResolvedValue([
{
open: 320,
high: 325,
low: 318,
close: 323,
volume: 1500000,
value: 480000000,
begin: '2026-05-01 00:00:00',
end: '2026-05-01 23:59:59',
},
]);
const result = await service.getCandles(
'shares',
'SBER',
CandleInterval.DAY,
'2026-05-01',
'2026-06-01',
);
expect(cache.getOrFetch).toHaveBeenCalledWith(
'candles',
['shares', 'SBER', '24', '2026-05-01', '2026-06-01'],
expect.any(Function),
'candlesTtl',
);
expect(moexCandles.getCandles).toHaveBeenCalledWith(
'stock',
'shares',
'SBER',
24,
'2026-05-01',
'2026-06-01',
);
expect(result).toEqual({
data: [
{
open: 320,
high: 325,
low: 318,
close: 323,
volume: 1500000,
value: 480000000,
begin: '2026-05-01 00:00:00',
end: '2026-05-01 23:59:59',
},
],
fromCache: false,
cachedAt: '2026-06-15T00:00:00.000Z',
});
});
it('uses MOEX interval 60 for hourly bond candles without live MOEX dependency', async () => {
vi.mocked(moexCandles.getCandles).mockResolvedValue([]);
await service.getCandles(
'bonds',
'SU26238RMFS5',
CandleInterval.HOUR,
'2026-05-01',
'2026-06-01',
);
expect(cache.getOrFetch).toHaveBeenCalledWith(
'candles',
['bonds', 'SU26238RMFS5', '60', '2026-05-01', '2026-06-01'],
expect.any(Function),
'candlesTtl',
);
expect(moexCandles.getCandles).toHaveBeenCalledWith(
'stock',
'bonds',
'SU26238RMFS5',
60,
'2026-05-01',
'2026-06-01',
);
});
});