rwadurian/backend/services/planting-service/test/app.e2e-spec.ts

373 lines
11 KiB
TypeScript

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);
});
});
});