TransferBalanceValidationTest.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace Tests\Unit\Transfer;
  3. use Tests\TestCase;
  4. use App\Module\Transfer\Logics\TransferLogic;
  5. use App\Module\Transfer\Models\TransferApp;
  6. use App\Module\Fund\Services\FundService;
  7. use App\Module\Fund\Logic\User as FundUser;
  8. use Illuminate\Foundation\Testing\RefreshDatabase;
  9. use Illuminate\Support\Facades\DB;
  10. /**
  11. * 划转余额验证测试
  12. *
  13. * 测试修复后的余额验证机制,确保不会再出现超额扣除问题
  14. */
  15. class TransferBalanceValidationTest extends TestCase
  16. {
  17. use RefreshDatabase;
  18. private $testUserId = 99999;
  19. private $testFundId = 2;
  20. private $testTransferAppId;
  21. protected function setUp(): void
  22. {
  23. parent::setUp();
  24. // 创建测试用的转账应用
  25. $this->testTransferAppId = $this->createTestTransferApp();
  26. // 创建测试用户的资金账户
  27. $this->createTestUserFund();
  28. }
  29. /**
  30. * 测试Fund模块余额验证机制
  31. * 验证修复后不会允许余额变成负数
  32. */
  33. public function testFundBalanceValidationPreventsNegativeBalance()
  34. {
  35. // 设置用户余额为1000
  36. $this->setUserBalance(1000);
  37. // 尝试扣除超额金额(2000)
  38. $result = FundUser::handle(
  39. $this->testUserId,
  40. $this->testFundId,
  41. -2000, // 扣除2000,但用户只有1000
  42. \App\Module\Fund\Enums\LOG_TYPE::TRADE,
  43. 'test-insufficient-funds',
  44. '测试余额不足'
  45. );
  46. // 应该返回错误信息
  47. $this->assertIsString($result);
  48. $this->assertStringContains('not-sufficient-funds', $result);
  49. // 验证用户余额没有被修改
  50. $fundService = new FundService($this->testUserId, $this->testFundId);
  51. $this->assertEquals(1000, $fundService->balance());
  52. }
  53. /**
  54. * 测试Transfer模块预先余额验证
  55. * 验证转出前会检查余额是否充足
  56. */
  57. public function testTransferPreBalanceValidation()
  58. {
  59. // 设置用户余额为500
  60. $this->setUserBalance(500);
  61. // 尝试转出超额金额(1000)
  62. $this->expectException(\Exception::class);
  63. $this->expectExceptionMessage('用户余额不足');
  64. TransferLogic::createTransferOutForThirdParty(
  65. $this->testTransferAppId,
  66. $this->testUserId,
  67. '1000', // 转出1000,但用户只有500
  68. null,
  69. '测试超额转出',
  70. []
  71. );
  72. }
  73. /**
  74. * 测试异常金额预警机制
  75. * 验证超大金额会被拒绝
  76. */
  77. public function testAbnormalAmountValidation()
  78. {
  79. // 设置用户余额为足够大的金额
  80. $this->setUserBalance(100000000);
  81. // 尝试转出异常大金额(超过1000万限制)
  82. $this->expectException(\Exception::class);
  83. TransferLogic::createTransferOutForThirdParty(
  84. $this->testTransferAppId,
  85. $this->testUserId,
  86. '20000000', // 2000万,超过1000万限制
  87. null,
  88. '测试异常大金额',
  89. []
  90. );
  91. }
  92. /**
  93. * 测试汇率验证机制
  94. * 验证汇率配置错误时会被拒绝
  95. */
  96. public function testExchangeRateValidation()
  97. {
  98. // 设置用户余额
  99. $this->setUserBalance(1000);
  100. // 修改转账应用的汇率为0(异常值)
  101. $app = TransferApp::find($this->testTransferAppId);
  102. $app->exchange_rate = 0;
  103. $app->save();
  104. // 尝试转出
  105. $this->expectException(\Exception::class);
  106. $this->expectExceptionMessage('汇率配置错误:汇率必须大于0');
  107. TransferLogic::createTransferOutForThirdParty(
  108. $this->testTransferAppId,
  109. $this->testUserId,
  110. '100',
  111. null,
  112. '测试汇率错误',
  113. []
  114. );
  115. }
  116. /**
  117. * 测试正常转出流程
  118. * 验证修复后正常转出仍然可以工作
  119. */
  120. public function testNormalTransferStillWorks()
  121. {
  122. // 设置用户余额为1000
  123. $this->setUserBalance(1000);
  124. // 正常转出100
  125. $order = TransferLogic::createTransferOutForThirdParty(
  126. $this->testTransferAppId,
  127. $this->testUserId,
  128. '100',
  129. null,
  130. '测试正常转出',
  131. []
  132. );
  133. // 验证订单创建成功
  134. $this->assertNotNull($order);
  135. $this->assertEquals('100', $order->amount);
  136. // 验证用户余额正确扣除(100 + 手续费)
  137. $fundService = new FundService($this->testUserId, $this->testFundId);
  138. $expectedBalance = 1000 - 100 - $order->fee_amount;
  139. $this->assertEquals($expectedBalance, $fundService->balance());
  140. }
  141. /**
  142. * 创建测试用的转账应用
  143. */
  144. private function createTestTransferApp(): int
  145. {
  146. return DB::table('kku_transfer_apps')->insertGetId([
  147. 'keyname' => 'test_app',
  148. 'title' => '测试应用',
  149. 'description' => '测试用转账应用',
  150. 'out_id2' => 1,
  151. 'out_id3' => 11,
  152. 'currency_id' => 2,
  153. 'fund_id' => $this->testFundId,
  154. 'fund_to_uid' => 1,
  155. 'fund_in_uid' => 1,
  156. 'exchange_rate' => '300.0000',
  157. 'is_enabled' => 1,
  158. 'allow_transfer_in' => 1,
  159. 'allow_transfer_out' => 1,
  160. 'fee_in_rate' => '0.0050',
  161. 'fee_out_rate' => '0.0100',
  162. 'fee_in_min' => '0.3000',
  163. 'fee_in_max' => '5.0000',
  164. 'fee_out_min' => '0.5000',
  165. 'fee_out_max' => '10.0000',
  166. 'fee_account_uid' => 1,
  167. 'created_at' => now(),
  168. 'updated_at' => now(),
  169. ]);
  170. }
  171. /**
  172. * 创建测试用户的资金账户
  173. */
  174. private function createTestUserFund(): void
  175. {
  176. DB::table('kku_fund')->insert([
  177. 'user_id' => $this->testUserId,
  178. 'fund_id' => $this->testFundId,
  179. 'balance' => '0.0000000000',
  180. 'create_time' => time(),
  181. 'update_time' => time(),
  182. ]);
  183. }
  184. /**
  185. * 设置用户余额
  186. */
  187. private function setUserBalance(float $balance): void
  188. {
  189. DB::table('kku_fund')
  190. ->where('user_id', $this->testUserId)
  191. ->where('fund_id', $this->testFundId)
  192. ->update([
  193. 'balance' => number_format($balance, 10, '.', ''),
  194. 'update_time' => time(),
  195. ]);
  196. }
  197. }