|
|
@@ -0,0 +1,225 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Tests\Unit\Transfer;
|
|
|
+
|
|
|
+use Tests\TestCase;
|
|
|
+use App\Module\Transfer\Logics\TransferLogic;
|
|
|
+use App\Module\Transfer\Models\TransferApp;
|
|
|
+use App\Module\Fund\Services\FundService;
|
|
|
+use App\Module\Fund\Logic\User as FundUser;
|
|
|
+use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 划转余额验证测试
|
|
|
+ *
|
|
|
+ * 测试修复后的余额验证机制,确保不会再出现超额扣除问题
|
|
|
+ */
|
|
|
+class TransferBalanceValidationTest extends TestCase
|
|
|
+{
|
|
|
+ use RefreshDatabase;
|
|
|
+
|
|
|
+ private $testUserId = 99999;
|
|
|
+ private $testFundId = 2;
|
|
|
+ private $testTransferAppId;
|
|
|
+
|
|
|
+ protected function setUp(): void
|
|
|
+ {
|
|
|
+ parent::setUp();
|
|
|
+
|
|
|
+ // 创建测试用的转账应用
|
|
|
+ $this->testTransferAppId = $this->createTestTransferApp();
|
|
|
+
|
|
|
+ // 创建测试用户的资金账户
|
|
|
+ $this->createTestUserFund();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试Fund模块余额验证机制
|
|
|
+ * 验证修复后不会允许余额变成负数
|
|
|
+ */
|
|
|
+ public function testFundBalanceValidationPreventsNegativeBalance()
|
|
|
+ {
|
|
|
+ // 设置用户余额为1000
|
|
|
+ $this->setUserBalance(1000);
|
|
|
+
|
|
|
+ // 尝试扣除超额金额(2000)
|
|
|
+ $result = FundUser::handle(
|
|
|
+ $this->testUserId,
|
|
|
+ $this->testFundId,
|
|
|
+ -2000, // 扣除2000,但用户只有1000
|
|
|
+ \App\Module\Fund\Enums\LOG_TYPE::TRADE,
|
|
|
+ 'test-insufficient-funds',
|
|
|
+ '测试余额不足'
|
|
|
+ );
|
|
|
+
|
|
|
+ // 应该返回错误信息
|
|
|
+ $this->assertIsString($result);
|
|
|
+ $this->assertStringContains('not-sufficient-funds', $result);
|
|
|
+
|
|
|
+ // 验证用户余额没有被修改
|
|
|
+ $fundService = new FundService($this->testUserId, $this->testFundId);
|
|
|
+ $this->assertEquals(1000, $fundService->balance());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试Transfer模块预先余额验证
|
|
|
+ * 验证转出前会检查余额是否充足
|
|
|
+ */
|
|
|
+ public function testTransferPreBalanceValidation()
|
|
|
+ {
|
|
|
+ // 设置用户余额为500
|
|
|
+ $this->setUserBalance(500);
|
|
|
+
|
|
|
+ // 尝试转出超额金额(1000)
|
|
|
+ $this->expectException(\Exception::class);
|
|
|
+ $this->expectExceptionMessage('用户余额不足');
|
|
|
+
|
|
|
+ TransferLogic::createTransferOutForThirdParty(
|
|
|
+ $this->testTransferAppId,
|
|
|
+ $this->testUserId,
|
|
|
+ '1000', // 转出1000,但用户只有500
|
|
|
+ null,
|
|
|
+ '测试超额转出',
|
|
|
+ []
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试异常金额预警机制
|
|
|
+ * 验证超大金额会被拒绝
|
|
|
+ */
|
|
|
+ public function testAbnormalAmountValidation()
|
|
|
+ {
|
|
|
+ // 设置用户余额为足够大的金额
|
|
|
+ $this->setUserBalance(100000000);
|
|
|
+
|
|
|
+ // 尝试转出异常大金额(超过1000万限制)
|
|
|
+ $this->expectException(\Exception::class);
|
|
|
+
|
|
|
+ TransferLogic::createTransferOutForThirdParty(
|
|
|
+ $this->testTransferAppId,
|
|
|
+ $this->testUserId,
|
|
|
+ '20000000', // 2000万,超过1000万限制
|
|
|
+ null,
|
|
|
+ '测试异常大金额',
|
|
|
+ []
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试汇率验证机制
|
|
|
+ * 验证汇率配置错误时会被拒绝
|
|
|
+ */
|
|
|
+ public function testExchangeRateValidation()
|
|
|
+ {
|
|
|
+ // 设置用户余额
|
|
|
+ $this->setUserBalance(1000);
|
|
|
+
|
|
|
+ // 修改转账应用的汇率为0(异常值)
|
|
|
+ $app = TransferApp::find($this->testTransferAppId);
|
|
|
+ $app->exchange_rate = 0;
|
|
|
+ $app->save();
|
|
|
+
|
|
|
+ // 尝试转出
|
|
|
+ $this->expectException(\Exception::class);
|
|
|
+ $this->expectExceptionMessage('汇率配置错误:汇率必须大于0');
|
|
|
+
|
|
|
+ TransferLogic::createTransferOutForThirdParty(
|
|
|
+ $this->testTransferAppId,
|
|
|
+ $this->testUserId,
|
|
|
+ '100',
|
|
|
+ null,
|
|
|
+ '测试汇率错误',
|
|
|
+ []
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 测试正常转出流程
|
|
|
+ * 验证修复后正常转出仍然可以工作
|
|
|
+ */
|
|
|
+ public function testNormalTransferStillWorks()
|
|
|
+ {
|
|
|
+ // 设置用户余额为1000
|
|
|
+ $this->setUserBalance(1000);
|
|
|
+
|
|
|
+ // 正常转出100
|
|
|
+ $order = TransferLogic::createTransferOutForThirdParty(
|
|
|
+ $this->testTransferAppId,
|
|
|
+ $this->testUserId,
|
|
|
+ '100',
|
|
|
+ null,
|
|
|
+ '测试正常转出',
|
|
|
+ []
|
|
|
+ );
|
|
|
+
|
|
|
+ // 验证订单创建成功
|
|
|
+ $this->assertNotNull($order);
|
|
|
+ $this->assertEquals('100', $order->amount);
|
|
|
+
|
|
|
+ // 验证用户余额正确扣除(100 + 手续费)
|
|
|
+ $fundService = new FundService($this->testUserId, $this->testFundId);
|
|
|
+ $expectedBalance = 1000 - 100 - $order->fee_amount;
|
|
|
+ $this->assertEquals($expectedBalance, $fundService->balance());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建测试用的转账应用
|
|
|
+ */
|
|
|
+ private function createTestTransferApp(): int
|
|
|
+ {
|
|
|
+ return DB::table('kku_transfer_apps')->insertGetId([
|
|
|
+ 'keyname' => 'test_app',
|
|
|
+ 'title' => '测试应用',
|
|
|
+ 'description' => '测试用转账应用',
|
|
|
+ 'out_id2' => 1,
|
|
|
+ 'out_id3' => 11,
|
|
|
+ 'currency_id' => 2,
|
|
|
+ 'fund_id' => $this->testFundId,
|
|
|
+ 'fund_to_uid' => 1,
|
|
|
+ 'fund_in_uid' => 1,
|
|
|
+ 'exchange_rate' => '300.0000',
|
|
|
+ 'is_enabled' => 1,
|
|
|
+ 'allow_transfer_in' => 1,
|
|
|
+ 'allow_transfer_out' => 1,
|
|
|
+ 'fee_in_rate' => '0.0050',
|
|
|
+ 'fee_out_rate' => '0.0100',
|
|
|
+ 'fee_in_min' => '0.3000',
|
|
|
+ 'fee_in_max' => '5.0000',
|
|
|
+ 'fee_out_min' => '0.5000',
|
|
|
+ 'fee_out_max' => '10.0000',
|
|
|
+ 'fee_account_uid' => 1,
|
|
|
+ 'created_at' => now(),
|
|
|
+ 'updated_at' => now(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建测试用户的资金账户
|
|
|
+ */
|
|
|
+ private function createTestUserFund(): void
|
|
|
+ {
|
|
|
+ DB::table('kku_fund')->insert([
|
|
|
+ 'user_id' => $this->testUserId,
|
|
|
+ 'fund_id' => $this->testFundId,
|
|
|
+ 'balance' => '0.0000000000',
|
|
|
+ 'create_time' => time(),
|
|
|
+ 'update_time' => time(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置用户余额
|
|
|
+ */
|
|
|
+ private function setUserBalance(float $balance): void
|
|
|
+ {
|
|
|
+ DB::table('kku_fund')
|
|
|
+ ->where('user_id', $this->testUserId)
|
|
|
+ ->where('fund_id', $this->testFundId)
|
|
|
+ ->update([
|
|
|
+ 'balance' => number_format($balance, 10, '.', ''),
|
|
|
+ 'update_time' => time(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+}
|