|
|
@@ -3,13 +3,22 @@ import { testClient } from 'hono/testing';
|
|
|
import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
|
|
|
import { UserEntityMt, RoleMt } from '@d8d/core-module-mt/user-module-mt/entities';
|
|
|
import { FileMt } from '@d8d/core-module-mt/file-module-mt/entities';
|
|
|
+import { OrderMt, OrderGoodsMt, OrderRefundMt } from '@d8d/orders-module-mt';
|
|
|
+import { PayStatus, PayType } from '@d8d/orders-module-mt';
|
|
|
+import { MerchantMt } from '@d8d/merchant-module-mt';
|
|
|
+import { SupplierMt } from '@d8d/supplier-module-mt';
|
|
|
+import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
|
|
|
+import { AreaEntityMt } from '@d8d/geo-areas-mt';
|
|
|
+import { SystemConfigMt } from '@d8d/core-module-mt/system-config-module-mt/entities';
|
|
|
+import { GoodsMt, GoodsCategoryMt } from '@d8d/goods-module-mt';
|
|
|
import creditBalanceRoutes from '../../src/routes';
|
|
|
import { CreditBalanceMt, CreditBalanceLogMt, CreditBalanceChangeType } from '../../src/entities';
|
|
|
import { CreditBalanceTestDataFactory } from '../utils/test-data-factory';
|
|
|
|
|
|
// 设置集成测试钩子
|
|
|
setupIntegrationDatabaseHooksWithEntities([
|
|
|
- UserEntityMt, RoleMt, FileMt, CreditBalanceMt, CreditBalanceLogMt
|
|
|
+ UserEntityMt, RoleMt, FileMt, CreditBalanceMt, CreditBalanceLogMt, OrderMt, OrderGoodsMt, OrderRefundMt,
|
|
|
+ MerchantMt, SupplierMt, DeliveryAddressMt, AreaEntityMt, SystemConfigMt, GoodsMt, GoodsCategoryMt
|
|
|
])
|
|
|
|
|
|
describe('多租户信用额度API集成测试', () => {
|
|
|
@@ -84,8 +93,20 @@ describe('多租户信用额度API集成测试', () => {
|
|
|
expect(data.id).toBeDefined();
|
|
|
expect(data.tenantId).toBe(1);
|
|
|
expect(data.userId).toBe(testUser.id);
|
|
|
+
|
|
|
+ // 验证金额字段的类型(应该是数字,不是字符串)
|
|
|
+ expect(typeof data.totalLimit).toBe('number');
|
|
|
+ expect(typeof data.usedAmount).toBe('number');
|
|
|
+ expect(typeof data.availableAmount).toBe('number');
|
|
|
+
|
|
|
+ // 验证金额值
|
|
|
expect(data.totalLimit).toBe(10000);
|
|
|
expect(data.availableAmount).toBe(10000);
|
|
|
+
|
|
|
+ // 验证可以安全调用toFixed方法
|
|
|
+ expect(() => data.totalLimit.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => data.usedAmount.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => data.availableAmount.toFixed(2)).not.toThrow();
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
@@ -129,9 +150,21 @@ describe('多租户信用额度API集成测试', () => {
|
|
|
expect(data.id).toBeDefined();
|
|
|
expect(data.tenantId).toBe(1);
|
|
|
expect(data.userId).toBe(testUser.id);
|
|
|
+
|
|
|
+ // 验证金额字段的类型(应该是数字,不是字符串)
|
|
|
+ expect(typeof data.totalLimit).toBe('number');
|
|
|
+ expect(typeof data.usedAmount).toBe('number');
|
|
|
+ expect(typeof data.availableAmount).toBe('number');
|
|
|
+
|
|
|
+ // 验证金额值
|
|
|
expect(data.totalLimit).toBe(8000);
|
|
|
expect(data.usedAmount).toBe(2000);
|
|
|
expect(data.availableAmount).toBe(6000);
|
|
|
+
|
|
|
+ // 验证可以安全调用toFixed方法
|
|
|
+ expect(() => data.totalLimit.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => data.usedAmount.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => data.availableAmount.toFixed(2)).not.toThrow();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
@@ -232,10 +265,29 @@ describe('多租户信用额度API集成测试', () => {
|
|
|
isEnabled: 1
|
|
|
});
|
|
|
|
|
|
+ // 创建测试订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_003',
|
|
|
+ amount: 500.00,
|
|
|
+ payAmount: 500.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
const response = await client.payment.$post({
|
|
|
json: {
|
|
|
- amount: 500.00,
|
|
|
- referenceId: 'ORD202412010001',
|
|
|
+ referenceId: testOrder.id.toString(), // 使用订单ID
|
|
|
operatorId: 1,
|
|
|
remark: '订单支付'
|
|
|
}
|
|
|
@@ -252,6 +304,588 @@ describe('多租户信用额度API集成测试', () => {
|
|
|
expect(data.availableAmount).toBeCloseTo(9500, 2); // 10000 - 500
|
|
|
}
|
|
|
});
|
|
|
+
|
|
|
+ it('应该成功扣减额度并更新订单状态', async () => {
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 使用测试数据工厂创建商户、供货商、地址和订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_001',
|
|
|
+ amount: 500.00,
|
|
|
+ payAmount: 500.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const orderRepo = dataSource.getRepository(OrderMt);
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ amount: 500.00,
|
|
|
+ referenceId: testOrder.id.toString(), // 使用订单ID作为referenceId
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.usedAmount).toBeCloseTo(500, 2);
|
|
|
+ expect(data.availableAmount).toBeCloseTo(9500, 2);
|
|
|
+
|
|
|
+ // 验证订单状态已更新
|
|
|
+ const updatedOrder = await orderRepo.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder!.payState).toBe(PayStatus.SUCCESS);
|
|
|
+ expect(updatedOrder!.payType).toBe(PayType.CREDIT);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('额度扣减失败时应该回滚订单状态更新', async () => {
|
|
|
+ // 先创建信用额度记录,但设置较低的额度
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 100.00, // 只有100额度
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 使用测试数据工厂创建商户、供货商、地址和订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_002',
|
|
|
+ amount: 500.00, // 订单金额500,超过额度
|
|
|
+ payAmount: 500.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const orderRepo = dataSource.getRepository(OrderMt);
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ amount: 500.00,
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回错误,因为额度不足
|
|
|
+ expect(response.status).toBe(500);
|
|
|
+
|
|
|
+ // 验证订单状态未更新
|
|
|
+ const updatedOrder = await orderRepo.findOne({
|
|
|
+ where: { id: testOrder.id, tenantId: 1 }
|
|
|
+ });
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder!.payState).toBe(PayStatus.UNPAID); // 仍然是未支付状态
|
|
|
+ expect(updatedOrder!.payType).toBe(0); // 支付类型未改变
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝支付已支付成功的订单', async () => {
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建已支付成功的测试订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_004',
|
|
|
+ amount: 500.00,
|
|
|
+ payAmount: 500.00,
|
|
|
+ payState: PayStatus.SUCCESS, // 已支付成功
|
|
|
+ payType: PayType.CREDIT
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回错误,因为订单已支付成功
|
|
|
+ expect(response.status).toBe(400);
|
|
|
+ if (response.status === 400) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.message).toContain('订单已支付成功');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝支付不存在的订单', async () => {
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: '99999', // 不存在的订单ID
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回404错误
|
|
|
+ expect(response.status).toBe(404);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝支付无效的订单ID', async () => {
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: 'invalid-order-id', // 无效的订单ID格式
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回400错误
|
|
|
+ expect(response.status).toBe(400);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝支付金额为0的订单', async () => {
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建支付金额为0的测试订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_005',
|
|
|
+ amount: 0.00,
|
|
|
+ payAmount: 0.00, // 支付金额为0
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回400错误
|
|
|
+ expect(response.status).toBe(400);
|
|
|
+ if (response.status === 400) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.message).toContain('订单支付金额无效');
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该成功扣减额度当用户已有欠款时', async () => {
|
|
|
+ // 先创建信用额度记录,用户已有欠款
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 1000.00,
|
|
|
+ usedAmount: 198.01, // 用户已有欠款
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建测试订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_006',
|
|
|
+ amount: 99.00,
|
|
|
+ payAmount: 99.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ // 已用额度应该是 198.01 + 99.00 = 297.01
|
|
|
+ expect(data.usedAmount).toBeCloseTo(297.01, 2);
|
|
|
+ // 可用额度应该是 1000.00 - 297.01 = 702.99
|
|
|
+ expect(data.availableAmount).toBeCloseTo(702.99, 2);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理decimal字段的字符串类型', async () => {
|
|
|
+ // 测试TypeORM decimal字段返回字符串的情况
|
|
|
+ // 先创建信用额度记录,使用字符串格式的金额
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+
|
|
|
+ // 直接使用SQL插入,模拟TypeORM返回字符串的情况
|
|
|
+ await dataSource.query(`
|
|
|
+ INSERT INTO credit_balance_mt (tenant_id, user_id, total_limit, used_amount, is_enabled)
|
|
|
+ VALUES (1, $1, '500.00', '150.75', 1)
|
|
|
+ `, [testUser.id]);
|
|
|
+
|
|
|
+ // 创建测试订单,使用字符串格式的金额
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'TEST_ORDER_007',
|
|
|
+ amount: 50.25,
|
|
|
+ payAmount: 50.25,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '订单支付'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ // 已用额度应该是 150.75 + 50.25 = 201.00
|
|
|
+ expect(data.usedAmount).toBeCloseTo(201.00, 2);
|
|
|
+ // 可用额度应该是 500.00 - 201.00 = 299.00
|
|
|
+ expect(data.availableAmount).toBeCloseTo(299.00, 2);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝支付其他用户的订单(越权访问防护)', async () => {
|
|
|
+ // 先为testUser创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为otherUser创建信用额度记录
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: otherUser.id,
|
|
|
+ totalLimit: 5000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为otherUser创建测试订单(注意:订单的用户ID是otherUser.id)
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, otherUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, otherUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, otherUser.id);
|
|
|
+
|
|
|
+ const otherUserOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ otherUser.id, // 这是关键:订单属于otherUser
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'OTHER_USER_ORDER',
|
|
|
+ amount: 300.00,
|
|
|
+ payAmount: 300.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // testUser尝试支付otherUser的订单
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: otherUserOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '尝试支付其他用户的订单'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}` // testUser的token
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回404,因为订单查询会失败(userId不匹配)
|
|
|
+ expect(response.status).toBe(404);
|
|
|
+ if (response.status === 404) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.message).toContain('订单不存在或无权访问');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证订单状态没有改变
|
|
|
+ const orderRepo = dataSource.getRepository(OrderMt);
|
|
|
+ const orderAfterAttempt = await orderRepo.findOne({
|
|
|
+ where: { id: otherUserOrder.id }
|
|
|
+ });
|
|
|
+ expect(orderAfterAttempt).toBeDefined();
|
|
|
+ expect(orderAfterAttempt!.payState).toBe(PayStatus.UNPAID); // 应该还是未支付状态
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该拒绝跨租户支付订单(租户隔离防护)', async () => {
|
|
|
+ // 为otherTenantUser创建信用额度记录(租户2)
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 2,
|
|
|
+ userId: otherTenantUser.id,
|
|
|
+ totalLimit: 8000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 为otherTenantUser创建测试订单(租户2)
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 2, otherTenantUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 2, otherTenantUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 2, otherTenantUser.id);
|
|
|
+
|
|
|
+ const otherTenantOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 2, // 租户2
|
|
|
+ otherTenantUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'OTHER_TENANT_ORDER',
|
|
|
+ amount: 400.00,
|
|
|
+ payAmount: 400.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // testUser(租户1)尝试支付otherTenantUser(租户2)的订单
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: otherTenantOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '尝试支付其他租户的订单'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}` // 租户1的用户
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 应该返回404,因为租户ID不匹配
|
|
|
+ expect(response.status).toBe(404);
|
|
|
+ if (response.status === 404) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.message).toContain('订单不存在或无权访问');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证订单状态没有改变
|
|
|
+ const orderRepo = dataSource.getRepository(OrderMt);
|
|
|
+ const orderAfterAttempt = await orderRepo.findOne({
|
|
|
+ where: { id: otherTenantOrder.id }
|
|
|
+ });
|
|
|
+ expect(orderAfterAttempt).toBeDefined();
|
|
|
+ expect(orderAfterAttempt!.payState).toBe(PayStatus.UNPAID);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理订单状态更新时的用户ID验证', async () => {
|
|
|
+ // 这个测试验证订单状态更新也包含用户ID检查
|
|
|
+ // 先创建信用额度记录
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ const creditBalanceRepo = dataSource.getRepository(CreditBalanceMt);
|
|
|
+ await creditBalanceRepo.save({
|
|
|
+ tenantId: 1,
|
|
|
+ userId: testUser.id,
|
|
|
+ totalLimit: 10000.00,
|
|
|
+ usedAmount: 0.00,
|
|
|
+ isEnabled: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建测试订单
|
|
|
+ const merchant = await CreditBalanceTestDataFactory.createTestMerchant(dataSource, 1, testUser.id);
|
|
|
+ const supplier = await CreditBalanceTestDataFactory.createTestSupplier(dataSource, 1, testUser.id);
|
|
|
+ const address = await CreditBalanceTestDataFactory.createTestDeliveryAddress(dataSource, 1, testUser.id);
|
|
|
+
|
|
|
+ const testOrder = await CreditBalanceTestDataFactory.createTestOrder(
|
|
|
+ dataSource,
|
|
|
+ 1,
|
|
|
+ testUser.id,
|
|
|
+ merchant.id,
|
|
|
+ supplier.id,
|
|
|
+ address.id,
|
|
|
+ {
|
|
|
+ orderNo: 'USER_ID_VALIDATION_ORDER',
|
|
|
+ amount: 250.00,
|
|
|
+ payAmount: 250.00,
|
|
|
+ payState: PayStatus.UNPAID
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 正常支付应该成功
|
|
|
+ const response = await client.payment.$post({
|
|
|
+ json: {
|
|
|
+ referenceId: testOrder.id.toString(),
|
|
|
+ operatorId: 1,
|
|
|
+ remark: '正常支付测试用户ID验证'
|
|
|
+ }
|
|
|
+ }, {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${userToken}`
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(response.status).toBe(200);
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(data.usedAmount).toBeCloseTo(250, 2);
|
|
|
+
|
|
|
+ // 验证订单状态已更新
|
|
|
+ const orderRepo = dataSource.getRepository(OrderMt);
|
|
|
+ const updatedOrder = await orderRepo.findOne({
|
|
|
+ where: { id: testOrder.id }
|
|
|
+ });
|
|
|
+ expect(updatedOrder).toBeDefined();
|
|
|
+ expect(updatedOrder!.payState).toBe(PayStatus.SUCCESS);
|
|
|
+ expect(updatedOrder!.payType).toBe(PayType.CREDIT);
|
|
|
+ }
|
|
|
+ });
|
|
|
});
|
|
|
|
|
|
describe('结账恢复额度', () => {
|
|
|
@@ -332,7 +966,30 @@ describe('多租户信用额度API集成测试', () => {
|
|
|
if (response.status === 200) {
|
|
|
const data = await response.json();
|
|
|
expect(data.data).toHaveLength(1);
|
|
|
- expect(data.data[0].changeAmount).toBeCloseTo(-500, 2);
|
|
|
+
|
|
|
+ const log = data.data[0];
|
|
|
+
|
|
|
+ // 验证变更记录金额字段的类型(应该是数字,不是字符串)
|
|
|
+ expect(typeof log.changeAmount).toBe('number');
|
|
|
+ expect(typeof log.beforeTotal).toBe('number');
|
|
|
+ expect(typeof log.afterTotal).toBe('number');
|
|
|
+ expect(typeof log.beforeUsed).toBe('number');
|
|
|
+ expect(typeof log.afterUsed).toBe('number');
|
|
|
+
|
|
|
+ // 验证金额值
|
|
|
+ expect(log.changeAmount).toBeCloseTo(-500, 2);
|
|
|
+ expect(log.beforeTotal).toBeCloseTo(10000, 2);
|
|
|
+ expect(log.afterTotal).toBeCloseTo(10000, 2);
|
|
|
+ expect(log.beforeUsed).toBeCloseTo(0, 2);
|
|
|
+ expect(log.afterUsed).toBeCloseTo(500, 2);
|
|
|
+
|
|
|
+ // 验证可以安全调用toFixed方法
|
|
|
+ expect(() => log.changeAmount.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => log.beforeTotal.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => log.afterTotal.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => log.beforeUsed.toFixed(2)).not.toThrow();
|
|
|
+ expect(() => log.afterUsed.toFixed(2)).not.toThrow();
|
|
|
+
|
|
|
expect(data.pagination.total).toBe(1);
|
|
|
}
|
|
|
});
|