import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe, ExecutionContext } from '@nestjs/common'; import * as request from 'supertest'; import { HealthController } from '../src/api/controllers/health.controller'; import { PlantingOrderController } from '../src/api/controllers/planting-order.controller'; import { PlantingPositionController } from '../src/api/controllers/planting-position.controller'; import { PlantingApplicationService } from '../src/application/services/planting-application.service'; import { JwtAuthGuard } from '../src/api/guards/jwt-auth.guard'; import { ConfigModule } from '@nestjs/config'; // Mock for PlantingApplicationService const mockPlantingService = { createOrder: jest.fn(), selectProvinceCity: jest.fn(), confirmProvinceCity: jest.fn(), payOrder: jest.fn(), getUserOrders: jest.fn(), getOrderDetail: jest.fn(), getUserPosition: jest.fn(), cancelOrder: jest.fn(), }; // Mock JwtAuthGuard that always rejects unauthorized requests const mockJwtAuthGuardReject = { canActivate: jest.fn().mockReturnValue(false), }; describe('PlantingController (e2e) - Unauthorized', () => { let app: INestApplication; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env.test', }), ], controllers: [HealthController, PlantingOrderController, PlantingPositionController], providers: [ { provide: PlantingApplicationService, useValue: mockPlantingService, }, ], }) .overrideGuard(JwtAuthGuard) .useValue(mockJwtAuthGuardReject) .compile(); app = moduleFixture.createNestApplication(); app.setGlobalPrefix('api/v1'); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.init(); }); afterAll(async () => { await app.close(); }); describe('/health (GET)', () => { it('应该返回健康状态', () => { return request(app.getHttpServer()) .get('/api/v1/health') .expect(200) .expect((res) => { expect(res.body.status).toBe('ok'); expect(res.body.service).toBe('planting-service'); }); }); }); describe('/health/ready (GET)', () => { it('应该返回就绪状态', () => { return request(app.getHttpServer()) .get('/api/v1/health/ready') .expect(200) .expect((res) => { expect(res.body.status).toBe('ready'); }); }); }); describe('/planting/orders (POST)', () => { it('应该拒绝未认证的请求', () => { return request(app.getHttpServer()) .post('/api/v1/planting/orders') .send({ treeCount: 1 }) .expect(403); }); }); describe('/planting/orders (GET)', () => { it('应该拒绝未认证的请求', () => { return request(app.getHttpServer()) .get('/api/v1/planting/orders') .expect(403); }); }); describe('/planting/position (GET)', () => { it('应该拒绝未认证的请求', () => { return request(app.getHttpServer()) .get('/api/v1/planting/position') .expect(403); }); }); }); describe('PlantingController (e2e) - Authorized', () => { let app: INestApplication; const mockUser = { id: '1', username: 'testuser' }; beforeAll(async () => { // Reset mocks jest.clearAllMocks(); const mockJwtAuthGuardAccept = { canActivate: (context: ExecutionContext) => { const req = context.switchToHttp().getRequest(); req.user = mockUser; return true; }, }; const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env.test', }), ], controllers: [HealthController, PlantingOrderController, PlantingPositionController], providers: [ { provide: PlantingApplicationService, useValue: mockPlantingService, }, ], }) .overrideGuard(JwtAuthGuard) .useValue(mockJwtAuthGuardAccept) .compile(); app = moduleFixture.createNestApplication(); app.setGlobalPrefix('api/v1'); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); await app.init(); }); afterAll(async () => { await app.close(); }); beforeEach(() => { jest.clearAllMocks(); }); describe('/planting/orders (POST)', () => { it('应该成功创建订单', async () => { const mockOrder = { orderNo: 'PO202411300001', userId: '1', treeCount: 5, totalAmount: 10995, status: 'CREATED', createdAt: new Date().toISOString(), }; mockPlantingService.createOrder.mockResolvedValue(mockOrder); const response = await request(app.getHttpServer()) .post('/api/v1/planting/orders') .send({ treeCount: 5 }) .expect(201); expect(response.body.orderNo).toBe('PO202411300001'); expect(response.body.treeCount).toBe(5); }); it('应该验证treeCount必须为正整数', async () => { await request(app.getHttpServer()) .post('/api/v1/planting/orders') .send({ treeCount: 0 }) .expect(400); await request(app.getHttpServer()) .post('/api/v1/planting/orders') .send({ treeCount: -1 }) .expect(400); }); it('应该验证treeCount不能超过最大限制', async () => { await request(app.getHttpServer()) .post('/api/v1/planting/orders') .send({ treeCount: 1001 }) .expect(400); }); }); describe('/planting/orders (GET)', () => { it('应该返回用户订单列表', async () => { const mockOrders = [ { orderNo: 'PO202411300001', treeCount: 5, totalAmount: 10995, status: 'CREATED', }, { orderNo: 'PO202411300002', treeCount: 3, totalAmount: 6597, status: 'PAID', }, ]; mockPlantingService.getUserOrders.mockResolvedValue(mockOrders); const response = await request(app.getHttpServer()) .get('/api/v1/planting/orders') .query({ page: 1, pageSize: 10 }) .expect(200); expect(response.body).toHaveLength(2); }); }); describe('/planting/orders/:orderNo (GET)', () => { it('应该返回订单详情', async () => { const mockOrder = { orderNo: 'PO202411300001', treeCount: 5, totalAmount: 10995, status: 'CREATED', }; mockPlantingService.getOrderDetail.mockResolvedValue(mockOrder); const response = await request(app.getHttpServer()) .get('/api/v1/planting/orders/PO202411300001') .expect(200); expect(response.body.orderNo).toBe('PO202411300001'); }); it('应该返回404当订单不存在', async () => { mockPlantingService.getOrderDetail.mockResolvedValue(null); await request(app.getHttpServer()) .get('/api/v1/planting/orders/NONEXISTENT') .expect(404); }); }); describe('/planting/position (GET)', () => { it('应该返回用户持仓信息', async () => { const mockPosition = { totalTreeCount: 10, effectiveTreeCount: 8, pendingTreeCount: 2, distributions: [ { provinceCode: '440000', cityCode: '440100', treeCount: 10 }, ], }; mockPlantingService.getUserPosition.mockResolvedValue(mockPosition); const response = await request(app.getHttpServer()) .get('/api/v1/planting/position') .expect(200); expect(response.body.totalTreeCount).toBe(10); expect(response.body.distributions).toHaveLength(1); }); }); describe('/planting/orders/:orderNo/select-province-city (POST)', () => { it('应该成功选择省市', async () => { mockPlantingService.selectProvinceCity.mockResolvedValue({ success: true, message: '省市选择成功', }); const response = await request(app.getHttpServer()) .post('/api/v1/planting/orders/PO202411300001/select-province-city') .send({ provinceCode: '440000', provinceName: '广东省', cityCode: '440100', cityName: '广州市', }) .expect(200); expect(response.body.success).toBe(true); }); it('应该验证省市参数', async () => { await request(app.getHttpServer()) .post('/api/v1/planting/orders/PO202411300001/select-province-city') .send({ provinceCode: '', provinceName: '广东省', }) .expect(400); }); }); describe('/planting/orders/:orderNo/confirm-province-city (POST)', () => { it('应该成功确认省市', async () => { mockPlantingService.confirmProvinceCity.mockResolvedValue({ success: true, message: '省市确认成功', }); const response = await request(app.getHttpServer()) .post('/api/v1/planting/orders/PO202411300001/confirm-province-city') .expect(200); expect(response.body.success).toBe(true); }); }); describe('/planting/orders/:orderNo/pay (POST)', () => { it('应该成功支付订单', async () => { const mockPayResult = { orderNo: 'PO202411300001', status: 'POOL_SCHEDULED', allocations: [ { targetType: 'POOL', amount: 1979.1 }, { targetType: 'OPERATION', amount: 109.95 }, ], }; mockPlantingService.payOrder.mockResolvedValue(mockPayResult); const response = await request(app.getHttpServer()) .post('/api/v1/planting/orders/PO202411300001/pay') .expect(200); expect(response.body.status).toBe('POOL_SCHEDULED'); expect(response.body.allocations).toHaveLength(2); }); }); describe('/planting/orders/:orderNo/cancel (POST)', () => { it('应该成功取消订单', async () => { mockPlantingService.cancelOrder.mockResolvedValue({ success: true, message: '订单取消成功', }); const response = await request(app.getHttpServer()) .post('/api/v1/planting/orders/PO202411300001/cancel') .expect(200); expect(response.body.success).toBe(true); }); }); });