MexMatchLogicBugFixTest.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. <?php
  2. namespace App\Module\Mex\Tests;
  3. use App\Module\Mex\Logic\MexMatchLogic;
  4. use App\Module\Mex\Models\MexOrder;
  5. use App\Module\Mex\Models\MexTransaction;
  6. use App\Module\Mex\Models\MexWarehouse;
  7. use App\Module\Mex\Models\MexPriceConfig;
  8. use App\Module\Mex\Enums\OrderStatus;
  9. use App\Module\Mex\Enums\OrderType;
  10. use App\Module\Mex\Enums\TransactionType;
  11. use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
  12. use Illuminate\Foundation\Testing\RefreshDatabase;
  13. use Illuminate\Support\Facades\DB;
  14. use Tests\TestCase;
  15. /**
  16. * Mex撮合逻辑Bug修复测试
  17. *
  18. * 测试修复后的撮合逻辑确保订单状态更新和成交记录创建的一致性
  19. */
  20. class MexMatchLogicBugFixTest extends TestCase
  21. {
  22. use RefreshDatabase;
  23. /**
  24. * 仓库账户ID
  25. */
  26. const WAREHOUSE_USER_ID = 15;
  27. /**
  28. * 测试用户ID
  29. */
  30. const TEST_USER_ID = 39999;
  31. /**
  32. * 测试商品ID
  33. */
  34. const TEST_ITEM_ID = 999;
  35. protected function setUp(): void
  36. {
  37. parent::setUp();
  38. // 创建测试数据
  39. $this->createTestData();
  40. }
  41. /**
  42. * 创建测试数据
  43. */
  44. private function createTestData()
  45. {
  46. // 创建价格配置
  47. MexPriceConfig::create([
  48. 'item_id' => self::TEST_ITEM_ID,
  49. 'min_price' => 10.00000,
  50. 'max_price' => 20.00000,
  51. 'protection_threshold' => 1000,
  52. 'is_enabled' => true,
  53. ]);
  54. // 创建仓库库存(用于买入测试)
  55. MexWarehouse::create([
  56. 'item_id' => self::TEST_ITEM_ID,
  57. 'quantity' => 1000,
  58. 'total_buy_quantity' => 0,
  59. 'total_buy_amount' => '0.00000',
  60. 'total_sell_quantity' => 0,
  61. 'total_sell_amount' => '0.00000',
  62. 'last_transaction_at' => now(),
  63. ]);
  64. }
  65. /**
  66. * 测试用户买入物品订单撮合的数据一致性
  67. */
  68. public function testUserBuyItemOrderMatchConsistency()
  69. {
  70. // 创建买入订单
  71. $order = MexOrder::create([
  72. 'user_id' => self::TEST_USER_ID,
  73. 'item_id' => self::TEST_ITEM_ID,
  74. 'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
  75. 'order_type' => OrderType::BUY,
  76. 'quantity' => 100,
  77. 'price' => 20.00000,
  78. 'total_amount' => 2000.00000,
  79. 'status' => OrderStatus::PENDING,
  80. 'frozen_amount' => 2000.00000,
  81. ]);
  82. $warehouse = MexWarehouse::where('item_id', self::TEST_ITEM_ID)->first();
  83. // 模拟撮合过程中的异常情况
  84. DB::beginTransaction();
  85. try {
  86. // 执行撮合(这里会因为资金流转失败而抛出异常,但我们要确保数据一致性)
  87. $result = MexMatchLogic::executeUserBuyItemOrderMatch($order, $warehouse);
  88. // 由于没有实际的资金和物品服务,这里会失败
  89. $this->assertFalse($result['success']);
  90. // 重要:确保订单状态没有被错误更新
  91. $order->refresh();
  92. $this->assertEquals(OrderStatus::PENDING, $order->status);
  93. // 确保没有创建成交记录
  94. $transactionCount = MexTransaction::where('buy_order_id', $order->id)->count();
  95. $this->assertEquals(0, $transactionCount);
  96. } catch (\Exception $e) {
  97. // 异常情况下也要确保数据一致性
  98. $order->refresh();
  99. $this->assertEquals(OrderStatus::PENDING, $order->status);
  100. $transactionCount = MexTransaction::where('buy_order_id', $order->id)->count();
  101. $this->assertEquals(0, $transactionCount);
  102. }
  103. DB::rollBack();
  104. }
  105. /**
  106. * 测试用户卖出物品订单撮合的数据一致性
  107. */
  108. public function testUserSellItemOrderMatchConsistency()
  109. {
  110. // 创建卖出订单
  111. $order = MexOrder::create([
  112. 'user_id' => self::TEST_USER_ID,
  113. 'item_id' => self::TEST_ITEM_ID,
  114. 'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
  115. 'order_type' => OrderType::SELL,
  116. 'quantity' => 100,
  117. 'price' => 10.00000,
  118. 'total_amount' => 1000.00000,
  119. 'status' => OrderStatus::PENDING,
  120. ]);
  121. // 模拟撮合过程中的异常情况
  122. DB::beginTransaction();
  123. try {
  124. // 执行撮合(这里会因为物品流转失败而抛出异常,但我们要确保数据一致性)
  125. $result = MexMatchLogic::executeUserSellItemOrderMatch($order);
  126. // 由于没有实际的资金和物品服务,这里会失败
  127. $this->assertFalse($result['success']);
  128. // 重要:确保订单状态没有被错误更新
  129. $order->refresh();
  130. $this->assertEquals(OrderStatus::PENDING, $order->status);
  131. // 确保没有创建成交记录
  132. $transactionCount = MexTransaction::where('sell_order_id', $order->id)->count();
  133. $this->assertEquals(0, $transactionCount);
  134. } catch (\Exception $e) {
  135. // 异常情况下也要确保数据一致性
  136. $order->refresh();
  137. $this->assertEquals(OrderStatus::PENDING, $order->status);
  138. $transactionCount = MexTransaction::where('sell_order_id', $order->id)->count();
  139. $this->assertEquals(0, $transactionCount);
  140. }
  141. DB::rollBack();
  142. }
  143. /**
  144. * 测试修复命令能正确识别数据不一致问题
  145. */
  146. public function testFixCommandCanIdentifyInconsistentData()
  147. {
  148. // 手动创建一个已完成但没有成交记录的订单(模拟bug情况)
  149. $order = MexOrder::create([
  150. 'user_id' => self::TEST_USER_ID,
  151. 'item_id' => self::TEST_ITEM_ID,
  152. 'currency_type' => FUND_CURRENCY_TYPE::DIAMOND,
  153. 'order_type' => OrderType::SELL,
  154. 'quantity' => 100,
  155. 'price' => 10.00000,
  156. 'total_amount' => 1000.00000,
  157. 'status' => OrderStatus::COMPLETED, // 已完成
  158. 'completed_quantity' => 100,
  159. 'completed_amount' => 1000.00000,
  160. 'completed_at' => now(),
  161. ]);
  162. // 验证订单没有对应的成交记录
  163. $this->assertFalse($order->buyTransactions()->exists());
  164. $this->assertFalse($order->sellTransactions()->exists());
  165. // 使用修复命令的查询逻辑
  166. $missingTransactionOrders = MexOrder::where('status', OrderStatus::COMPLETED)
  167. ->whereDoesntHave('buyTransactions')
  168. ->whereDoesntHave('sellTransactions')
  169. ->get();
  170. $this->assertCount(1, $missingTransactionOrders);
  171. $this->assertEquals($order->id, $missingTransactionOrders->first()->id);
  172. }
  173. }