feat(FEAT-MO-004): 实现指数板块详情分析页

This commit is contained in:
2026-08-25 00:16:43 +08:00
parent f8f4da93dc
commit 0131f863b6
32 changed files with 2262 additions and 8 deletions
@@ -0,0 +1,120 @@
import type { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import request from 'supertest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { MarketDetailController } from '../src/market-overview/detail/market-detail.controller';
import { MarketDetailService } from '../src/market-overview/detail/market-detail.service';
import { TDX_CLIENT_PORT } from '../src/market-overview/market-overview.service';
const indexResponse = {
ErrorId: '0',
Value: {
'000001.SH': {
Date: ['20260817', '20260818'],
Open: ['100', '102'],
High: ['103', '106'],
Low: ['99', '101'],
Close: ['102', '105'],
Amount: ['1000', '1200'],
Volume: ['100', '120'],
},
},
};
describe('市场详情 HTTP API', () => {
let app: INestApplication | undefined;
afterEach(async () => {
await app?.close();
app = undefined;
});
it('返回指数详情并隐藏 TDX 原始字段', async () => {
const client = { request: vi.fn().mockResolvedValue(indexResponse) };
const moduleRef = await Test.createTestingModule({
controllers: [MarketDetailController],
providers: [MarketDetailService, { provide: TDX_CLIENT_PORT, useValue: client }],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api/v1');
await app.init();
const response = await request(app.getHttpServer())
.get('/api/v1/market-overview/indices/000001.SH/detail?count=2')
.expect(200);
expect(response.body).toMatchObject({
source: 'tdx',
data: {
instrument: { code: '000001.SH', name: '上证指数', type: 'index' },
series: [{ time: '20260817' }, { time: '20260818' }],
availableViews: { kline: true, intraday: false },
},
});
expect(JSON.stringify(response.body)).not.toContain('ErrorId');
});
it('拒绝越界数量和非法代码且不调用 TDX', async () => {
const client = { request: vi.fn() };
const moduleRef = await Test.createTestingModule({
controllers: [MarketDetailController],
providers: [MarketDetailService, { provide: TDX_CLIENT_PORT, useValue: client }],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api/v1');
await app.init();
await request(app.getHttpServer())
.get('/api/v1/market-overview/indices/not-a-code/detail')
.expect(400);
await request(app.getHttpServer())
.get('/api/v1/market-overview/indices/000001.SH/detail?count=121')
.expect(400);
expect(client.request).not.toHaveBeenCalled();
});
it('板块详情先读目录再返回行业 K 线', async () => {
const client = {
request: vi
.fn()
.mockResolvedValueOnce({
ErrorId: '0',
Value: [{ Code: '881001.SH', Name: '行业总览' }],
})
.mockResolvedValueOnce({
ErrorId: '0',
Value: {
'881001.SH': {
Date: ['20260817', '20260818'],
Open: ['100', '102'],
High: ['103', '106'],
Low: ['99', '101'],
Close: ['102', '105'],
Amount: ['1000', '1200'],
Volume: ['100', '120'],
},
},
}),
};
const moduleRef = await Test.createTestingModule({
controllers: [MarketDetailController],
providers: [MarketDetailService, { provide: TDX_CLIENT_PORT, useValue: client }],
}).compile();
app = moduleRef.createNestApplication();
app.setGlobalPrefix('api/v1');
await app.init();
const response = await request(app.getHttpServer())
.get('/api/v1/market-overview/sectors/881001.SH/detail')
.expect(200);
expect(response.body.data.instrument).toEqual({
code: '881001.SH',
name: '行业总览',
type: 'sector',
});
expect(client.request).toHaveBeenCalledTimes(2);
});
});