refactor(docs): 简化README结构,更新技术栈和项目结构描述
This commit is contained in:
226
backend/tests/unit/controllers/UserController.test.js
Normal file
226
backend/tests/unit/controllers/UserController.test.js
Normal 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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
179
backend/tests/unit/services/AuthService.test.js
Normal file
179
backend/tests/unit/services/AuthService.test.js
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
307
backend/tests/unit/services/OrderService.test.js
Normal file
307
backend/tests/unit/services/OrderService.test.js
Normal 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('数据库错误');
|
||||
});
|
||||
});
|
||||
});
|
||||
323
backend/tests/unit/services/PaymentService.test.js
Normal file
323
backend/tests/unit/services/PaymentService.test.js
Normal 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)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
423
backend/tests/unit/services/TransportService.test.js
Normal file
423
backend/tests/unit/services/TransportService.test.js
Normal 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位数字
|
||||
});
|
||||
});
|
||||
});
|
||||
150
backend/tests/unit/services/UserService.test.js
Normal file
150
backend/tests/unit/services/UserService.test.js
Normal 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('用户不存在');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user