refactor(docs): 简化README结构,更新技术栈和项目结构描述

This commit is contained in:
2025-09-20 15:19:59 +08:00
parent cec08f89e2
commit b8c9e5c959
54 changed files with 14343 additions and 6124 deletions

View File

@@ -0,0 +1,226 @@
const UserController = require('../../../src/controllers/UserController');
const User = require('../../../src/models/User');
const { successResponse, errorResponse, paginatedResponse } = require('../../../src/utils/response');
// Mock dependencies
jest.mock('../../../src/models/User');
jest.mock('../../../src/utils/response');
describe('UserController', () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
body: {},
query: {},
user: { id: 1, user_type: 'admin' }
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis()
};
next = jest.fn();
jest.clearAllMocks();
});
describe('getUserList', () => {
it('应该成功获取用户列表', async () => {
const mockUsers = [
{
id: 1,
uuid: 'uuid1',
username: 'user1',
real_name: 'Real User 1',
phone: '13800138001',
email: 'user1@example.com',
user_type: 'user',
status: 'active',
avatar_url: 'avatar1.jpg',
created_at: new Date()
}
];
User.findAndCountAll.mockResolvedValue({
count: 1,
rows: mockUsers
});
paginatedResponse.mockReturnValue({
code: 200,
message: '获取成功',
data: {
users: mockUsers,
total: 1,
page: 1,
pageSize: 10
}
});
req.query = { page: '1', pageSize: '10' };
await UserController.getUserList(req, res);
expect(User.findAndCountAll).toHaveBeenCalledWith({
where: {},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
expect(res.json).toHaveBeenCalled();
});
it('应该处理数据库错误', async () => {
const error = new Error('数据库错误');
User.findAndCountAll.mockRejectedValue(error);
errorResponse.mockReturnValue({
code: 500,
message: '服务器内部错误'
});
await UserController.getUserList(req, res);
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalled();
});
});
describe('getUserDetail', () => {
it('应该成功获取用户详情', async () => {
const mockUser = {
id: 1,
uuid: 'uuid1',
username: 'user1',
real_name: 'Real User 1',
phone: '13800138001',
email: 'user1@example.com',
user_type: 'user',
status: 'active',
avatar_url: 'avatar1.jpg',
created_at: new Date()
};
User.findByPk.mockResolvedValue(mockUser);
successResponse.mockReturnValue({
code: 200,
message: '获取用户详情成功',
data: mockUser
});
req.params.id = '1';
await UserController.getUserDetail(req, res);
expect(User.findByPk).toHaveBeenCalledWith('1');
expect(res.json).toHaveBeenCalled();
});
it('应该处理用户不存在的情况', async () => {
User.findByPk.mockResolvedValue(null);
errorResponse.mockReturnValue({
code: 404,
message: '用户不存在'
});
req.params.id = '999';
await UserController.getUserDetail(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalled();
});
});
describe('updateUser', () => {
it('应该成功更新用户信息', async () => {
User.update.mockResolvedValue([1]); // 返回更新的行数
successResponse.mockReturnValue({
code: 200,
message: '用户信息更新成功'
});
req.params.id = '1';
req.body = { real_name: '新用户名', email: 'new@example.com' };
await UserController.updateUser(req, res, next);
expect(User.update).toHaveBeenCalledWith(
{ real_name: '新用户名', email: 'new@example.com' },
{ where: { id: '1' } }
);
expect(res.json).toHaveBeenCalled();
});
it('应该处理用户不存在的情况', async () => {
User.update.mockResolvedValue([0]); // 返回0表示没有更新任何行
errorResponse.mockReturnValue({
code: 404,
message: '用户不存在'
});
req.params.id = '999';
req.body = { real_name: '新用户名' };
await UserController.updateUser(req, res, next);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalled();
});
});
describe('updateUserStatus', () => {
it('应该成功更新用户状态', async () => {
User.update.mockResolvedValue([1]); // 返回更新的行数
successResponse.mockReturnValue({
code: 200,
message: '用户状态更新成功'
});
req.params.id = '1';
req.body = { status: 'inactive' };
await UserController.updateUserStatus(req, res, next);
expect(User.update).toHaveBeenCalledWith(
{ status: 'inactive' },
{ where: { id: '1' } }
);
expect(res.json).toHaveBeenCalled();
});
it('应该处理用户不存在的情况', async () => {
User.update.mockResolvedValue([0]); // 返回0表示没有更新任何行
errorResponse.mockReturnValue({
code: 404,
message: '用户不存在'
});
req.params.id = '999';
req.body = { status: 'inactive' };
await UserController.updateUserStatus(req, res, next);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalled();
});
});
});

View File

@@ -0,0 +1,179 @@
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
const AuthService = require('../../../src/services/AuthService');
const User = require('../../../src/models/User');
const { jwtConfig } = require('../../../src/config/config');
// Mock依赖
jest.mock('../../../src/models/User');
jest.mock('jsonwebtoken');
jest.mock('bcryptjs');
jest.mock('uuid');
describe('AuthService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('miniProgramLogin', () => {
const mockPhone = '13800138000';
const mockCode = '123456';
const mockMiniProgramType = 'client';
const mockUuid = 'test-uuid-123';
const mockUser = {
id: 1,
uuid: mockUuid,
username: `user_${mockPhone}`,
phone: mockPhone,
user_type: mockMiniProgramType,
real_name: '测试用户',
avatar_url: 'http://example.com/avatar.jpg'
};
beforeEach(() => {
uuidv4.mockReturnValue(mockUuid);
bcrypt.hashSync.mockReturnValue('hashed_password');
jwt.sign.mockReturnValue('mock_jwt_token');
});
test('验证码错误时应该抛出异常', async () => {
await expect(AuthService.miniProgramLogin(mockPhone, '000000', mockMiniProgramType))
.rejects.toThrow('验证码错误');
});
test('用户存在时应该返回用户信息和token', async () => {
// 模拟用户已存在
User.findOne.mockResolvedValue(mockUser);
const result = await AuthService.miniProgramLogin(mockPhone, mockCode, mockMiniProgramType);
expect(User.findOne).toHaveBeenCalledWith({ where: { phone: mockPhone } });
expect(User.create).not.toHaveBeenCalled();
expect(jwt.sign).toHaveBeenCalledWith(
{
id: mockUser.id,
uuid: mockUser.uuid,
username: mockUser.username,
phone: mockUser.phone,
userType: mockUser.user_type
},
jwtConfig.secret,
{ expiresIn: jwtConfig.expiresIn }
);
expect(result).toEqual({
token: 'mock_jwt_token',
userInfo: {
id: mockUser.id,
username: mockUser.username,
realName: mockUser.real_name,
avatar: mockUser.avatar_url,
userType: mockUser.user_type,
phone: mockUser.phone
}
});
});
test('用户不存在时应该创建新用户并返回信息', async () => {
// 模拟用户不存在
User.findOne.mockResolvedValue(null);
User.create.mockResolvedValue(mockUser);
const result = await AuthService.miniProgramLogin(mockPhone, mockCode, mockMiniProgramType);
expect(User.findOne).toHaveBeenCalledWith({ where: { phone: mockPhone } });
expect(uuidv4).toHaveBeenCalled();
expect(bcrypt.hashSync).toHaveBeenCalledWith(mockPhone, 10);
expect(User.create).toHaveBeenCalledWith({
uuid: mockUuid,
username: `user_${mockPhone}`,
phone: mockPhone,
user_type: mockMiniProgramType,
password_hash: 'hashed_password'
});
expect(result).toEqual({
token: 'mock_jwt_token',
userInfo: {
id: mockUser.id,
username: mockUser.username,
realName: mockUser.real_name,
avatar: mockUser.avatar_url,
userType: mockUser.user_type,
phone: mockUser.phone
}
});
});
test('未指定小程序类型时应该使用默认值client', async () => {
User.findOne.mockResolvedValue(null);
User.create.mockResolvedValue({
...mockUser,
user_type: 'client'
});
await AuthService.miniProgramLogin(mockPhone, mockCode);
expect(User.create).toHaveBeenCalledWith({
uuid: mockUuid,
username: `user_${mockPhone}`,
phone: mockPhone,
user_type: 'client',
password_hash: 'hashed_password'
});
});
test('数据库操作失败时应该抛出异常', async () => {
User.findOne.mockRejectedValue(new Error('数据库连接失败'));
await expect(AuthService.miniProgramLogin(mockPhone, mockCode, mockMiniProgramType))
.rejects.toThrow('数据库连接失败');
});
});
describe('verifyToken', () => {
const mockToken = 'valid_jwt_token';
const mockDecodedToken = {
id: 1,
uuid: 'test-uuid-123',
username: 'testuser',
phone: '13800138000',
userType: 'client'
};
test('有效token应该返回解码后的信息', async () => {
jwt.verify.mockReturnValue(mockDecodedToken);
const result = await AuthService.verifyToken(mockToken);
expect(jwt.verify).toHaveBeenCalledWith(mockToken, jwtConfig.secret);
expect(result).toEqual(mockDecodedToken);
});
test('无效token应该抛出异常', async () => {
jwt.verify.mockImplementation(() => {
throw new Error('jwt malformed');
});
await expect(AuthService.verifyToken('invalid_token'))
.rejects.toThrow('无效的token');
});
test('过期token应该抛出异常', async () => {
jwt.verify.mockImplementation(() => {
throw new Error('jwt expired');
});
await expect(AuthService.verifyToken('expired_token'))
.rejects.toThrow('无效的token');
});
test('空token应该抛出异常', async () => {
jwt.verify.mockImplementation(() => {
throw new Error('jwt must be provided');
});
await expect(AuthService.verifyToken(''))
.rejects.toThrow('无效的token');
});
});
});

View File

@@ -0,0 +1,307 @@
const OrderService = require('../../../src/services/OrderService');
const Order = require('../../../src/models/Order');
// Mock依赖
jest.mock('../../../src/models/Order');
describe('OrderService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('createOrder', () => {
const mockUserId = 1;
const mockOrderData = {
product_name: '测试商品',
quantity: 100,
unit_price: 50.00,
total_amount: 5000.00
};
const mockCreatedOrder = {
id: 1,
order_no: 'ORD1234567890123',
buyer_id: mockUserId,
...mockOrderData
};
beforeEach(() => {
// Mock Date.now() 和 Math.random()
jest.spyOn(Date, 'now').mockReturnValue(1234567890123);
jest.spyOn(Math, 'random').mockReturnValue(0.123);
});
afterEach(() => {
Date.now.mockRestore();
Math.random.mockRestore();
});
test('应该成功创建订单', async () => {
Order.create.mockResolvedValue(mockCreatedOrder);
const result = await OrderService.createOrder(mockOrderData, mockUserId);
expect(Order.create).toHaveBeenCalledWith({
...mockOrderData,
buyer_id: mockUserId,
order_no: 'ORD1234567890123123'
});
expect(result).toEqual(mockCreatedOrder);
});
test('数据库创建失败时应该抛出异常', async () => {
Order.create.mockRejectedValue(new Error('数据库错误'));
await expect(OrderService.createOrder(mockOrderData, mockUserId))
.rejects.toThrow('数据库错误');
});
test('应该生成唯一的订单号', async () => {
Order.create.mockResolvedValue(mockCreatedOrder);
await OrderService.createOrder(mockOrderData, mockUserId);
const expectedOrderNo = 'ORD1234567890123123';
expect(Order.create).toHaveBeenCalledWith(
expect.objectContaining({
order_no: expectedOrderNo
})
);
});
});
describe('getOrderList', () => {
const mockQuery = {
page: 1,
pageSize: 10,
status: 'pending',
orderNo: 'ORD123'
};
const mockUser = {
id: 1,
userType: 'client'
};
const mockOrders = [
{ id: 1, order_no: 'ORD123', buyer_id: 1 },
{ id: 2, order_no: 'ORD124', buyer_id: 1 }
];
const mockResult = {
count: 2,
rows: mockOrders
};
test('客户端用户应该只能查看自己的订单', async () => {
Order.findAndCountAll.mockResolvedValue(mockResult);
const result = await OrderService.getOrderList(mockQuery, mockUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: {
buyer_id: mockUser.id,
status: mockQuery.status,
order_no: mockQuery.orderNo
},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
expect(result).toEqual({
orders: mockOrders,
count: 2,
page: 1,
pageSize: 10
});
});
test('贸易商用户应该只能查看自己的订单', async () => {
const traderUser = { id: 2, userType: 'trader' };
Order.findAndCountAll.mockResolvedValue(mockResult);
await OrderService.getOrderList(mockQuery, traderUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: {
trader_id: traderUser.id,
status: mockQuery.status,
order_no: mockQuery.orderNo
},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
test('供应商用户应该只能查看自己的订单', async () => {
const supplierUser = { id: 3, userType: 'supplier' };
Order.findAndCountAll.mockResolvedValue(mockResult);
await OrderService.getOrderList(mockQuery, supplierUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: {
supplier_id: supplierUser.id,
status: mockQuery.status,
order_no: mockQuery.orderNo
},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
test('司机用户应该只能查看自己的订单', async () => {
const driverUser = { id: 4, userType: 'driver' };
Order.findAndCountAll.mockResolvedValue(mockResult);
await OrderService.getOrderList(mockQuery, driverUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: {
driver_id: driverUser.id,
status: mockQuery.status,
order_no: mockQuery.orderNo
},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
test('应该正确处理分页参数', async () => {
const paginationQuery = { page: 2, pageSize: 20 };
Order.findAndCountAll.mockResolvedValue(mockResult);
await OrderService.getOrderList(paginationQuery, mockUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: { buyer_id: mockUser.id },
limit: 20,
offset: 20,
order: [['created_at', 'DESC']]
});
});
test('应该使用默认分页参数', async () => {
Order.findAndCountAll.mockResolvedValue(mockResult);
await OrderService.getOrderList({}, mockUser);
expect(Order.findAndCountAll).toHaveBeenCalledWith({
where: { buyer_id: mockUser.id },
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
});
describe('getOrderDetail', () => {
const mockOrderId = 1;
const mockOrder = {
id: 1,
order_no: 'ORD123',
buyer_id: 1,
trader_id: 2,
supplier_id: 3,
driver_id: 4
};
test('应该返回订单详情', async () => {
const mockUser = { id: 1, userType: 'client' };
Order.findByPk.mockResolvedValue(mockOrder);
const result = await OrderService.getOrderDetail(mockOrderId, mockUser);
expect(Order.findByPk).toHaveBeenCalledWith(mockOrderId);
expect(result).toEqual(mockOrder);
});
test('订单不存在时应该抛出异常', async () => {
const mockUser = { id: 1, userType: 'client' };
Order.findByPk.mockResolvedValue(null);
await expect(OrderService.getOrderDetail(mockOrderId, mockUser))
.rejects.toThrow('订单不存在');
});
test('客户端用户无权限访问其他用户订单时应该抛出异常', async () => {
const mockUser = { id: 999, userType: 'client' };
Order.findByPk.mockResolvedValue(mockOrder);
await expect(OrderService.getOrderDetail(mockOrderId, mockUser))
.rejects.toThrow('无权限访问该订单');
});
test('贸易商用户无权限访问其他用户订单时应该抛出异常', async () => {
const mockUser = { id: 999, userType: 'trader' };
Order.findByPk.mockResolvedValue(mockOrder);
await expect(OrderService.getOrderDetail(mockOrderId, mockUser))
.rejects.toThrow('无权限访问该订单');
});
test('供应商用户无权限访问其他用户订单时应该抛出异常', async () => {
const mockUser = { id: 999, userType: 'supplier' };
Order.findByPk.mockResolvedValue(mockOrder);
await expect(OrderService.getOrderDetail(mockOrderId, mockUser))
.rejects.toThrow('无权限访问该订单');
});
test('司机用户无权限访问其他用户订单时应该抛出异常', async () => {
const mockUser = { id: 999, userType: 'driver' };
Order.findByPk.mockResolvedValue(mockOrder);
await expect(OrderService.getOrderDetail(mockOrderId, mockUser))
.rejects.toThrow('无权限访问该订单');
});
});
describe('updateOrderStatus', () => {
const mockOrderId = 1;
const mockStatus = 'confirmed';
const mockUser = { id: 1, userType: 'client' };
const mockOrder = {
id: 1,
order_no: 'ORD123',
buyer_id: 1,
status: 'pending'
};
test('应该成功更新订单状态', async () => {
Order.findByPk.mockResolvedValue(mockOrder);
Order.update.mockResolvedValue([1]); // 返回更新的行数
const result = await OrderService.updateOrderStatus(mockOrderId, mockStatus, mockUser);
expect(Order.findByPk).toHaveBeenCalledWith(mockOrderId);
expect(Order.update).toHaveBeenCalledWith(
{ status: mockStatus },
{ where: { id: mockOrderId } }
);
expect(result).toBe(true);
});
test('订单不存在时应该抛出异常', async () => {
Order.findByPk.mockResolvedValue(null);
await expect(OrderService.updateOrderStatus(mockOrderId, mockStatus, mockUser))
.rejects.toThrow('订单不存在');
});
test('更新失败时应该抛出异常', async () => {
Order.findByPk.mockResolvedValue(mockOrder);
Order.update.mockResolvedValue([0]); // 返回0表示没有更新任何行
await expect(OrderService.updateOrderStatus(mockOrderId, mockStatus, mockUser))
.rejects.toThrow('订单状态更新失败');
});
test('数据库更新操作失败时应该抛出异常', async () => {
Order.findByPk.mockResolvedValue(mockOrder);
Order.update.mockRejectedValue(new Error('数据库错误'));
await expect(OrderService.updateOrderStatus(mockOrderId, mockStatus, mockUser))
.rejects.toThrow('数据库错误');
});
});
});

View File

@@ -0,0 +1,323 @@
const PaymentService = require('../../../src/services/PaymentService');
const Payment = require('../../../src/models/Payment');
// Mock依赖
jest.mock('../../../src/models/Payment');
describe('PaymentService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('createPayment', () => {
const mockUserId = 1;
const mockPaymentData = {
orderId: 1,
amount: 100.00,
paymentType: 'order',
paymentMethod: 'wechat'
};
const mockCreatedPayment = {
id: 1,
order_id: 1,
user_id: mockUserId,
amount: 100.00,
payment_type: 'order',
payment_method: 'wechat',
payment_no: 'PAY1234567890123123',
status: 'pending'
};
beforeEach(() => {
// Mock Date.now() 和 Math.random()
jest.spyOn(Date, 'now').mockReturnValue(1234567890123);
jest.spyOn(Math, 'random').mockReturnValue(0.123);
});
afterEach(() => {
Date.now.mockRestore();
Math.random.mockRestore();
});
test('应该成功创建支付记录', async () => {
Payment.create.mockResolvedValue(mockCreatedPayment);
const result = await PaymentService.createPayment(mockPaymentData, mockUserId);
expect(Payment.create).toHaveBeenCalledWith({
order_id: mockPaymentData.orderId,
user_id: mockUserId,
amount: mockPaymentData.amount,
payment_type: mockPaymentData.paymentType,
payment_method: mockPaymentData.paymentMethod,
payment_no: 'PAY1234567890123123',
status: 'pending'
});
expect(result).toEqual({
payment: mockCreatedPayment,
paymentParams: {
paymentNo: 'PAY1234567890123123',
amount: mockPaymentData.amount,
paymentType: mockPaymentData.paymentType,
paymentMethod: mockPaymentData.paymentMethod
}
});
});
test('应该生成唯一的支付单号', async () => {
Payment.create.mockResolvedValue(mockCreatedPayment);
await PaymentService.createPayment(mockPaymentData, mockUserId);
const expectedPaymentNo = 'PAY1234567890123123';
expect(Payment.create).toHaveBeenCalledWith(
expect.objectContaining({
payment_no: expectedPaymentNo
})
);
});
test('数据库创建失败时应该抛出异常', async () => {
Payment.create.mockRejectedValue(new Error('数据库错误'));
await expect(PaymentService.createPayment(mockPaymentData, mockUserId))
.rejects.toThrow('数据库错误');
});
test('应该设置初始状态为pending', async () => {
Payment.create.mockResolvedValue(mockCreatedPayment);
await PaymentService.createPayment(mockPaymentData, mockUserId);
expect(Payment.create).toHaveBeenCalledWith(
expect.objectContaining({
status: 'pending'
})
);
});
});
describe('getPaymentList', () => {
const mockUserId = 1;
const mockQuery = {
page: 1,
pageSize: 10,
status: 'paid'
};
const mockPayments = [
{ id: 1, payment_no: 'PAY123', user_id: 1, status: 'paid' },
{ id: 2, payment_no: 'PAY124', user_id: 1, status: 'paid' }
];
const mockResult = {
count: 2,
rows: mockPayments
};
test('应该返回用户的支付列表', async () => {
Payment.findAndCountAll.mockResolvedValue(mockResult);
const result = await PaymentService.getPaymentList(mockQuery, mockUserId);
expect(Payment.findAndCountAll).toHaveBeenCalledWith({
where: {
user_id: mockUserId,
status: mockQuery.status
},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
expect(result).toEqual({
payments: mockPayments,
count: 2,
page: 1,
pageSize: 10
});
});
test('应该正确处理分页参数', async () => {
const paginationQuery = { page: 2, pageSize: 20 };
Payment.findAndCountAll.mockResolvedValue(mockResult);
await PaymentService.getPaymentList(paginationQuery, mockUserId);
expect(Payment.findAndCountAll).toHaveBeenCalledWith({
where: { user_id: mockUserId },
limit: 20,
offset: 20,
order: [['created_at', 'DESC']]
});
});
test('应该使用默认分页参数', async () => {
Payment.findAndCountAll.mockResolvedValue(mockResult);
await PaymentService.getPaymentList({}, mockUserId);
expect(Payment.findAndCountAll).toHaveBeenCalledWith({
where: { user_id: mockUserId },
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
test('不指定状态时应该查询所有状态的支付记录', async () => {
const queryWithoutStatus = { page: 1, pageSize: 10 };
Payment.findAndCountAll.mockResolvedValue(mockResult);
await PaymentService.getPaymentList(queryWithoutStatus, mockUserId);
expect(Payment.findAndCountAll).toHaveBeenCalledWith({
where: { user_id: mockUserId },
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
test('数据库查询失败时应该抛出异常', async () => {
Payment.findAndCountAll.mockRejectedValue(new Error('数据库错误'));
await expect(PaymentService.getPaymentList(mockQuery, mockUserId))
.rejects.toThrow('数据库错误');
});
});
describe('getPaymentDetail', () => {
const mockPaymentId = 1;
const mockUserId = 1;
const mockPayment = {
id: 1,
payment_no: 'PAY123',
user_id: 1,
amount: 100.00,
status: 'paid'
};
test('应该返回支付详情', async () => {
Payment.findByPk.mockResolvedValue(mockPayment);
const result = await PaymentService.getPaymentDetail(mockPaymentId, mockUserId);
expect(Payment.findByPk).toHaveBeenCalledWith(mockPaymentId);
expect(result).toEqual(mockPayment);
});
test('支付记录不存在时应该抛出异常', async () => {
Payment.findByPk.mockResolvedValue(null);
await expect(PaymentService.getPaymentDetail(mockPaymentId, mockUserId))
.rejects.toThrow('支付记录不存在');
});
test('用户无权限访问其他用户的支付记录时应该抛出异常', async () => {
const otherUserPayment = { ...mockPayment, user_id: 999 };
Payment.findByPk.mockResolvedValue(otherUserPayment);
await expect(PaymentService.getPaymentDetail(mockPaymentId, mockUserId))
.rejects.toThrow('无权限访问该支付记录');
});
test('数据库查询失败时应该抛出异常', async () => {
Payment.findByPk.mockRejectedValue(new Error('数据库错误'));
await expect(PaymentService.getPaymentDetail(mockPaymentId, mockUserId))
.rejects.toThrow('数据库错误');
});
});
describe('updatePaymentStatus', () => {
const mockPaymentNo = 'PAY123456789';
const mockStatus = 'paid';
const mockThirdPartyId = 'wx_123456789';
beforeEach(() => {
// Mock Date constructor
jest.spyOn(global, 'Date').mockImplementation(() => ({
toISOString: () => '2024-01-01T00:00:00.000Z'
}));
});
afterEach(() => {
global.Date.mockRestore();
});
test('应该成功更新支付状态为已支付', async () => {
Payment.update.mockResolvedValue([1]); // 返回更新的行数
const result = await PaymentService.updatePaymentStatus(mockPaymentNo, mockStatus, mockThirdPartyId);
expect(Payment.update).toHaveBeenCalledWith(
{
status: mockStatus,
third_party_id: mockThirdPartyId,
paid_time: expect.any(Object)
},
{
where: { payment_no: mockPaymentNo }
}
);
expect(result).toBe(true);
});
test('应该成功更新支付状态为失败', async () => {
const failedStatus = 'failed';
Payment.update.mockResolvedValue([1]);
const result = await PaymentService.updatePaymentStatus(mockPaymentNo, failedStatus, mockThirdPartyId);
expect(Payment.update).toHaveBeenCalledWith(
{
status: failedStatus,
third_party_id: mockThirdPartyId,
paid_time: null
},
{
where: { payment_no: mockPaymentNo }
}
);
expect(result).toBe(true);
});
test('支付记录不存在时应该抛出异常', async () => {
Payment.update.mockResolvedValue([0]); // 返回0表示没有更新任何行
await expect(PaymentService.updatePaymentStatus(mockPaymentNo, mockStatus, mockThirdPartyId))
.rejects.toThrow('支付记录不存在');
});
test('数据库更新操作失败时应该抛出异常', async () => {
Payment.update.mockRejectedValue(new Error('数据库错误'));
await expect(PaymentService.updatePaymentStatus(mockPaymentNo, mockStatus, mockThirdPartyId))
.rejects.toThrow('数据库错误');
});
test('只有支付成功时才设置支付时间', async () => {
Payment.update.mockResolvedValue([1]);
await PaymentService.updatePaymentStatus(mockPaymentNo, 'paid', mockThirdPartyId);
expect(Payment.update).toHaveBeenCalledWith(
expect.objectContaining({
paid_time: expect.any(Object)
}),
expect.any(Object)
);
});
test('支付失败时不设置支付时间', async () => {
Payment.update.mockResolvedValue([1]);
await PaymentService.updatePaymentStatus(mockPaymentNo, 'failed', mockThirdPartyId);
expect(Payment.update).toHaveBeenCalledWith(
expect.objectContaining({
paid_time: null
}),
expect.any(Object)
);
});
});
});

View File

@@ -0,0 +1,423 @@
const TransportService = require('../../../src/services/TransportService');
const { Transport, TransportTrack, Driver, Vehicle, Order } = require('../../../src/models');
// Mock 模型
jest.mock('../../../src/models', () => ({
Transport: {
create: jest.fn(),
findByPk: jest.fn(),
findAndCountAll: jest.fn(),
},
TransportTrack: {
create: jest.fn(),
findAll: jest.fn(),
},
Driver: {
findByPk: jest.fn(),
},
Vehicle: {
findByPk: jest.fn(),
},
Order: {},
}));
describe('TransportService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('createTransport', () => {
it('应该成功创建运输任务', async () => {
const transportData = {
order_id: 1,
pickup_address: '北京市朝阳区',
delivery_address: '上海市浦东新区',
expected_weight: 1000,
};
const mockTransport = { id: 1, ...transportData, transport_no: 'T202401010001' };
// Mock generateTransportNo 方法
jest.spyOn(TransportService, 'generateTransportNo').mockResolvedValue('T202401010001');
Transport.create.mockResolvedValue(mockTransport);
TransportTrack.create.mockResolvedValue({ id: 1 });
const result = await TransportService.createTransport(transportData);
expect(Transport.create).toHaveBeenCalledWith({
...transportData,
transport_no: 'T202401010001',
status: 'pending',
start_time: null,
end_time: null,
});
expect(TransportTrack.create).toHaveBeenCalledWith({
transport_id: 1,
status: 'pending',
location: '待发车',
description: '运输任务已创建,等待司机接单',
recorded_at: expect.any(Date),
});
expect(result).toEqual(mockTransport);
});
it('数据库创建失败时应该抛出异常', async () => {
const transportData = { order_id: 1 };
jest.spyOn(TransportService, 'generateTransportNo').mockResolvedValue('T202401010001');
Transport.create.mockRejectedValue(new Error('数据库错误'));
await expect(TransportService.createTransport(transportData))
.rejects.toThrow('创建运输任务失败: 数据库错误');
});
});
describe('getTransportList', () => {
it('应该返回运输任务列表', async () => {
const query = { page: 1, pageSize: 10 };
const mockResult = {
count: 2,
rows: [
{ id: 1, transport_no: 'T202401010001' },
{ id: 2, transport_no: 'T202401010002' },
],
};
Transport.findAndCountAll.mockResolvedValue(mockResult);
const result = await TransportService.getTransportList(query);
expect(Transport.findAndCountAll).toHaveBeenCalledWith({
where: {},
include: expect.any(Array),
limit: 10,
offset: 0,
order: [['created_at', 'DESC']],
});
expect(result).toEqual({
transports: mockResult.rows,
total: 2,
page: 1,
pageSize: 10,
totalPages: 1,
});
});
it('应该正确处理查询条件', async () => {
const query = {
page: 1,
pageSize: 10,
status: 'pending',
driverId: 1,
vehicleId: 2,
orderId: 3,
keyword: 'test',
startDate: '2024-01-01',
endDate: '2024-01-31',
};
Transport.findAndCountAll.mockResolvedValue({ count: 0, rows: [] });
await TransportService.getTransportList(query);
expect(Transport.findAndCountAll).toHaveBeenCalledWith({
where: {
status: 'pending',
driver_id: 1,
vehicle_id: 2,
order_id: 3,
[require('sequelize').Op.or]: [
{ transport_no: { [require('sequelize').Op.like]: '%test%' } },
{ pickup_address: { [require('sequelize').Op.like]: '%test%' } },
{ delivery_address: { [require('sequelize').Op.like]: '%test%' } },
],
created_at: {
[require('sequelize').Op.between]: [
new Date('2024-01-01'),
new Date('2024-01-31'),
],
},
},
include: expect.any(Array),
limit: 10,
offset: 0,
order: [['created_at', 'DESC']],
});
});
it('数据库查询失败时应该抛出异常', async () => {
Transport.findAndCountAll.mockRejectedValue(new Error('数据库错误'));
await expect(TransportService.getTransportList({}))
.rejects.toThrow('获取运输任务列表失败: 数据库错误');
});
});
describe('getTransportDetail', () => {
it('应该返回运输任务详情', async () => {
const mockTransport = {
id: 1,
transport_no: 'T202401010001',
status: 'pending',
};
Transport.findByPk.mockResolvedValue(mockTransport);
const result = await TransportService.getTransportDetail(1);
expect(Transport.findByPk).toHaveBeenCalledWith(1, {
include: expect.any(Array),
});
expect(result).toEqual(mockTransport);
});
it('运输任务不存在时应该抛出异常', async () => {
Transport.findByPk.mockResolvedValue(null);
await expect(TransportService.getTransportDetail(999))
.rejects.toThrow('获取运输任务详情失败: 运输任务不存在');
});
it('数据库查询失败时应该抛出异常', async () => {
Transport.findByPk.mockRejectedValue(new Error('数据库错误'));
await expect(TransportService.getTransportDetail(1))
.rejects.toThrow('获取运输任务详情失败: 数据库错误');
});
});
describe('updateTransportStatus', () => {
it('应该成功更新运输任务状态', async () => {
const mockTransport = {
id: 1,
status: 'pending',
update: jest.fn().mockResolvedValue(true),
};
Transport.findByPk.mockResolvedValue(mockTransport);
jest.spyOn(TransportService, 'createTrackRecord').mockResolvedValue({});
const result = await TransportService.updateTransportStatus(1, 'assigned', {
location: '调度中心',
description: '已分配司机',
});
expect(mockTransport.update).toHaveBeenCalledWith({
status: 'assigned',
location: '调度中心',
description: '已分配司机',
});
expect(TransportService.createTrackRecord).toHaveBeenCalledWith(
1,
'assigned',
'调度中心',
'已分配司机'
);
expect(result).toEqual(mockTransport);
});
it('运输任务不存在时应该抛出异常', async () => {
Transport.findByPk.mockResolvedValue(null);
await expect(TransportService.updateTransportStatus(999, 'assigned'))
.rejects.toThrow('更新运输任务状态失败: 运输任务不存在');
});
it('无效状态转换时应该抛出异常', async () => {
const mockTransport = {
id: 1,
status: 'completed',
};
Transport.findByPk.mockResolvedValue(mockTransport);
await expect(TransportService.updateTransportStatus(1, 'pending'))
.rejects.toThrow('更新运输任务状态失败: 无法从状态 completed 转换到 pending');
});
it('状态为in_transit时应该设置开始时间', async () => {
const mockTransport = {
id: 1,
status: 'assigned',
start_time: null,
update: jest.fn().mockResolvedValue(true),
};
Transport.findByPk.mockResolvedValue(mockTransport);
jest.spyOn(TransportService, 'createTrackRecord').mockResolvedValue({});
await TransportService.updateTransportStatus(1, 'in_transit');
expect(mockTransport.update).toHaveBeenCalledWith({
status: 'in_transit',
start_time: expect.any(Date),
});
});
it('状态为completed时应该设置结束时间', async () => {
const mockTransport = {
id: 1,
status: 'delivered',
update: jest.fn().mockResolvedValue(true),
};
Transport.findByPk.mockResolvedValue(mockTransport);
jest.spyOn(TransportService, 'createTrackRecord').mockResolvedValue({});
await TransportService.updateTransportStatus(1, 'completed');
expect(mockTransport.update).toHaveBeenCalledWith({
status: 'completed',
end_time: expect.any(Date),
});
});
});
describe('assignDriverAndVehicle', () => {
it('应该成功分配司机和车辆', async () => {
const mockTransport = {
id: 1,
status: 'pending',
update: jest.fn().mockResolvedValue(true),
};
const mockDriver = {
id: 1,
name: '张三',
status: 'available',
update: jest.fn().mockResolvedValue(true),
};
const mockVehicle = {
id: 1,
plate_number: '京A12345',
status: 'available',
update: jest.fn().mockResolvedValue(true),
};
Transport.findByPk.mockResolvedValue(mockTransport);
Driver.findByPk.mockResolvedValue(mockDriver);
Vehicle.findByPk.mockResolvedValue(mockVehicle);
jest.spyOn(TransportService, 'createTrackRecord').mockResolvedValue({});
const result = await TransportService.assignDriverAndVehicle(1, 1, 1);
expect(mockTransport.update).toHaveBeenCalledWith({
driver_id: 1,
vehicle_id: 1,
status: 'assigned',
});
expect(mockDriver.update).toHaveBeenCalledWith({ status: 'busy' });
expect(mockVehicle.update).toHaveBeenCalledWith({ status: 'in_use' });
expect(result).toEqual(mockTransport);
});
it('运输任务不存在时应该抛出异常', async () => {
Transport.findByPk.mockResolvedValue(null);
await expect(TransportService.assignDriverAndVehicle(999, 1, 1))
.rejects.toThrow('分配司机和车辆失败: 运输任务不存在');
});
it('运输任务状态不是pending时应该抛出异常', async () => {
const mockTransport = { id: 1, status: 'assigned' };
Transport.findByPk.mockResolvedValue(mockTransport);
await expect(TransportService.assignDriverAndVehicle(1, 1, 1))
.rejects.toThrow('分配司机和车辆失败: 只能为待分配的运输任务分配司机和车辆');
});
it('司机不存在或不可用时应该抛出异常', async () => {
const mockTransport = { id: 1, status: 'pending' };
Transport.findByPk.mockResolvedValue(mockTransport);
Driver.findByPk.mockResolvedValue(null);
await expect(TransportService.assignDriverAndVehicle(1, 999, 1))
.rejects.toThrow('分配司机和车辆失败: 司机不存在或不可用');
});
it('车辆不存在或不可用时应该抛出异常', async () => {
const mockTransport = { id: 1, status: 'pending' };
const mockDriver = { id: 1, status: 'available' };
Transport.findByPk.mockResolvedValue(mockTransport);
Driver.findByPk.mockResolvedValue(mockDriver);
Vehicle.findByPk.mockResolvedValue(null);
await expect(TransportService.assignDriverAndVehicle(1, 1, 999))
.rejects.toThrow('分配司机和车辆失败: 车辆不存在或不可用');
});
});
describe('createTrackRecord', () => {
beforeEach(() => {
// 恢复 createTrackRecord 方法的原始实现
if (TransportService.createTrackRecord.mockRestore) {
TransportService.createTrackRecord.mockRestore();
}
});
it('应该成功创建跟踪记录', async () => {
const mockTrack = {
id: 1,
transport_id: 1,
status: 'pending',
location: '北京',
description: '测试描述',
};
TransportTrack.create.mockResolvedValue(mockTrack);
const result = await TransportService.createTrackRecord(1, 'pending', '北京', '测试描述');
expect(TransportTrack.create).toHaveBeenCalledWith({
transport_id: 1,
status: 'pending',
location: '北京',
description: '测试描述',
recorded_at: expect.any(Date),
});
expect(result).toEqual(mockTrack);
});
it('数据库创建失败时应该抛出异常', async () => {
TransportTrack.create.mockRejectedValue(new Error('数据库错误'));
await expect(TransportService.createTrackRecord(1, 'pending'))
.rejects.toThrow('创建跟踪记录失败: 数据库错误');
});
});
describe('getTransportTracks', () => {
it('应该返回运输跟踪记录列表', async () => {
const mockTracks = [
{ id: 1, status: 'pending', recorded_at: new Date() },
{ id: 2, status: 'assigned', recorded_at: new Date() },
];
TransportTrack.findAll.mockResolvedValue(mockTracks);
const result = await TransportService.getTransportTracks(1);
expect(TransportTrack.findAll).toHaveBeenCalledWith({
where: { transport_id: 1 },
order: [['recorded_at', 'DESC']],
});
expect(result).toEqual(mockTracks);
});
it('数据库查询失败时应该抛出异常', async () => {
TransportTrack.findAll.mockRejectedValue(new Error('数据库错误'));
await expect(TransportService.getTransportTracks(1))
.rejects.toThrow('获取运输跟踪记录失败: 数据库错误');
});
});
describe('generateTransportNo', () => {
it('应该生成唯一的运输单号', async () => {
// 由于这个方法的实现可能涉及日期和随机数,我们只测试它返回字符串
const result = await TransportService.generateTransportNo();
expect(typeof result).toBe('string');
expect(result).toMatch(/^T\d{12}$/); // 格式T + 12位数字
});
});
});

View File

@@ -0,0 +1,150 @@
const UserService = require('../../../src/services/UserService');
const User = require('../../../src/models/User');
// Mock User模型
jest.mock('../../../src/models/User');
describe('UserService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('getUserList', () => {
it('应该成功获取用户列表', async () => {
const mockUsers = [
{
id: 1,
uuid: 'uuid-1',
username: 'user1',
real_name: '用户1',
phone: '13800138001',
email: 'user1@example.com',
user_type: 'customer',
status: 'active',
avatar_url: 'avatar1.jpg',
created_at: new Date()
}
];
User.findAndCountAll.mockResolvedValue({
count: 1,
rows: mockUsers
});
const result = await UserService.getUserList({ page: 1, pageSize: 10 });
expect(User.findAndCountAll).toHaveBeenCalledWith({
where: {},
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
expect(result).toEqual({
users: [{
id: 1,
uuid: 'uuid-1',
username: 'user1',
realName: '用户1',
phone: '13800138001',
email: 'user1@example.com',
userType: 'customer',
status: 'active',
avatar: 'avatar1.jpg',
createdAt: mockUsers[0].created_at
}],
count: 1,
page: 1,
pageSize: 10
});
});
it('应该根据条件过滤用户列表', async () => {
User.findAndCountAll.mockResolvedValue({
count: 0,
rows: []
});
await UserService.getUserList({
page: 1,
pageSize: 10,
userType: 'admin',
status: 'active'
});
expect(User.findAndCountAll).toHaveBeenCalledWith({
where: { user_type: 'admin', status: 'active' },
limit: 10,
offset: 0,
order: [['created_at', 'DESC']]
});
});
});
describe('getUserDetail', () => {
it('应该成功获取用户详情', async () => {
const mockUser = {
id: 1,
uuid: 'uuid-1',
username: 'user1',
real_name: '用户1',
phone: '13800138001',
email: 'user1@example.com',
user_type: 'customer',
status: 'active',
avatar_url: 'avatar1.jpg',
created_at: new Date(),
updated_at: new Date()
};
User.findByPk.mockResolvedValue(mockUser);
const result = await UserService.getUserDetail(1);
expect(User.findByPk).toHaveBeenCalledWith(1);
expect(result.id).toBe(1);
expect(result.username).toBe('user1');
});
it('用户不存在时应该抛出错误', async () => {
User.findByPk.mockResolvedValue(null);
await expect(UserService.getUserDetail(999)).rejects.toThrow('用户不存在');
});
});
describe('updateUser', () => {
it('应该成功更新用户信息', async () => {
User.update.mockResolvedValue([1]);
const updateData = { real_name: '新用户名' };
const result = await UserService.updateUser(1, updateData);
expect(User.update).toHaveBeenCalledWith(updateData, { where: { id: 1 } });
expect(result).toBe(true);
});
it('用户不存在时应该抛出错误', async () => {
User.update.mockResolvedValue([0]);
await expect(UserService.updateUser(999, {})).rejects.toThrow('用户不存在');
});
});
describe('updateUserStatus', () => {
it('应该成功更新用户状态', async () => {
User.update.mockResolvedValue([1]);
const result = await UserService.updateUserStatus(1, 'inactive');
expect(User.update).toHaveBeenCalledWith({ status: 'inactive' }, { where: { id: 1 } });
expect(result).toBe(true);
});
it('用户不存在时应该抛出错误', async () => {
User.update.mockResolvedValue([0]);
await expect(UserService.updateUserStatus(999, 'inactive')).rejects.toThrow('用户不存在');
});
});
});