373 lines
11 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|