|
@@ -0,0 +1,326 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Module\Transfer\Tests\Feature;
|
|
|
|
|
+
|
|
|
|
|
+use App\Module\Transfer\Models\TransferApp;
|
|
|
|
|
+use App\Module\Transfer\Models\TransferOrder;
|
|
|
|
|
+use App\Module\Transfer\Services\TransferService;
|
|
|
|
|
+use App\Module\Transfer\Enums\TransferType;
|
|
|
|
|
+use App\Module\Transfer\Enums\TransferStatus;
|
|
|
|
|
+use App\Module\Transfer\Jobs\ProcessTransferOrderJob;
|
|
|
|
|
+use App\Module\Transfer\Jobs\SendCallbackJob;
|
|
|
|
|
+use Tests\TestCase;
|
|
|
|
|
+use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
+use Illuminate\Support\Facades\Queue;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Transfer业务流程功能测试
|
|
|
|
|
+ */
|
|
|
|
|
+class TransferFlowTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ use RefreshDatabase;
|
|
|
|
|
+
|
|
|
|
|
+ protected TransferApp $internalApp;
|
|
|
|
|
+ protected TransferApp $externalApp;
|
|
|
|
|
+
|
|
|
|
|
+ protected function setUp(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ parent::setUp();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建农场内部模式应用
|
|
|
|
|
+ $this->internalApp = TransferApp::create([
|
|
|
|
|
+ 'keyname' => 'internal_app',
|
|
|
|
|
+ 'title' => '农场内部应用',
|
|
|
|
|
+ 'description' => '农场内部模式测试应用',
|
|
|
|
|
+ 'out_id' => 1001,
|
|
|
|
|
+ 'currency_id' => 2,
|
|
|
|
|
+ 'fund_id' => 2,
|
|
|
|
|
+ 'exchange_rate' => 1.0000,
|
|
|
|
|
+ 'is_enabled' => true,
|
|
|
|
|
+ // 所有API URL都为空,表示农场内部模式
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 创建外部API模式应用
|
|
|
|
|
+ $this->externalApp = TransferApp::create([
|
|
|
|
|
+ 'keyname' => 'external_app',
|
|
|
|
|
+ 'title' => '外部API应用',
|
|
|
|
|
+ 'description' => '外部API模式测试应用',
|
|
|
|
|
+ 'out_id' => 1002,
|
|
|
|
|
+ 'currency_id' => 2,
|
|
|
|
|
+ 'fund_id' => 2,
|
|
|
|
|
+ 'exchange_rate' => 2.0000,
|
|
|
|
|
+ 'is_enabled' => true,
|
|
|
|
|
+ 'order_callback_url' => 'https://api.example.com/callback',
|
|
|
|
|
+ 'order_out_create_url' => 'https://api.example.com/transfer/create',
|
|
|
|
|
+ 'order_out_info_url' => 'https://api.example.com/transfer/info',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试农场内部模式转入流程
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_internal_mode_transfer_in_flow()
|
|
|
|
|
+ {
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'business_id' => 'INTERNAL_IN_' . time(),
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '100.0000000000',
|
|
|
|
|
+ 'remark' => '农场内部转入测试',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建转入订单
|
|
|
|
|
+ $result = TransferService::createTransferIn($data);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单创建成功
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals(TransferType::IN, $result->type);
|
|
|
|
|
+
|
|
|
|
|
+ // 农场内部模式应该直接完成
|
|
|
|
|
+ $this->assertEquals(TransferStatus::COMPLETED, $result->status);
|
|
|
|
|
+ $this->assertNotNull($result->completedAt);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证数据库记录
|
|
|
|
|
+ $order = TransferOrder::find($result->id);
|
|
|
|
|
+ $this->assertNotNull($order);
|
|
|
|
|
+ $this->assertEquals(TransferStatus::COMPLETED, $order->status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试农场内部模式转出流程
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_internal_mode_transfer_out_flow()
|
|
|
|
|
+ {
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '50.0000000000',
|
|
|
|
|
+ 'password' => 'test123',
|
|
|
|
|
+ 'remark' => '农场内部转出测试',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建转出订单
|
|
|
|
|
+ $result = TransferService::createTransferOut($data);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单创建成功
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals(TransferType::OUT, $result->type);
|
|
|
|
|
+
|
|
|
|
|
+ // 农场内部模式应该直接完成
|
|
|
|
|
+ $this->assertEquals(TransferStatus::COMPLETED, $result->status);
|
|
|
|
|
+ $this->assertNotNull($result->completedAt);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试外部API模式转入流程
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_external_mode_transfer_in_flow()
|
|
|
|
|
+ {
|
|
|
|
|
+ Queue::fake();
|
|
|
|
|
+
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ 'transfer_app_id' => $this->externalApp->id,
|
|
|
|
|
+ 'business_id' => 'EXTERNAL_IN_' . time(),
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '200.0000000000', // 外部金额
|
|
|
|
|
+ 'remark' => '外部API转入测试',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建转入订单
|
|
|
|
|
+ $result = TransferService::createTransferIn($data);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单创建成功
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals(TransferType::IN, $result->type);
|
|
|
|
|
+ $this->assertEquals(TransferStatus::PROCESSING, $result->status);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证汇率计算(外部金额200,汇率2.0,内部金额应该是100)
|
|
|
|
|
+ $this->assertEquals('100.0000000000', $result->amount);
|
|
|
|
|
+ $this->assertEquals('200.0000000000', $result->outAmount);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证回调任务被调度
|
|
|
|
|
+ Queue::assertPushed(SendCallbackJob::class);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试外部API模式转出流程
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_external_mode_transfer_out_flow()
|
|
|
|
|
+ {
|
|
|
|
|
+ Queue::fake();
|
|
|
|
|
+
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ 'transfer_app_id' => $this->externalApp->id,
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '100.0000000000', // 外部金额
|
|
|
|
|
+ 'password' => 'test123',
|
|
|
|
|
+ 'remark' => '外部API转出测试',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ // 创建转出订单
|
|
|
|
|
+ $result = TransferService::createTransferOut($data);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单创建成功
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals(TransferType::OUT, $result->type);
|
|
|
|
|
+ $this->assertEquals(TransferStatus::PROCESSING, $result->status);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证汇率计算(外部金额100,汇率2.0,内部金额应该是200)
|
|
|
|
|
+ $this->assertEquals('200.0000000000', $result->amount);
|
|
|
|
|
+ $this->assertEquals('100.0000000000', $result->outAmount);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单处理任务被调度
|
|
|
|
|
+ Queue::assertPushed(ProcessTransferOrderJob::class);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试订单状态流转
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_order_status_flow()
|
|
|
|
|
+ {
|
|
|
|
|
+ // 创建一个处理中的订单
|
|
|
|
|
+ $order = TransferOrder::create([
|
|
|
|
|
+ 'transfer_app_id' => $this->externalApp->id,
|
|
|
|
|
+ 'out_order_id' => 'STATUS_FLOW_' . time(),
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'type' => TransferType::OUT,
|
|
|
|
|
+ 'status' => TransferStatus::CREATED,
|
|
|
|
|
+ 'out_amount' => '100.0000000000',
|
|
|
|
|
+ 'amount' => '200.0000000000',
|
|
|
|
|
+ 'exchange_rate' => 2.0000,
|
|
|
|
|
+ 'currency_id' => 2,
|
|
|
|
|
+ 'fund_id' => 2,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 测试状态更新
|
|
|
|
|
+ $this->assertTrue($order->updateStatus(TransferStatus::PROCESSING));
|
|
|
|
|
+ $this->assertEquals(TransferStatus::PROCESSING, $order->fresh()->status);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertTrue($order->updateStatus(TransferStatus::COMPLETED));
|
|
|
|
|
+ $this->assertEquals(TransferStatus::COMPLETED, $order->fresh()->status);
|
|
|
|
|
+ $this->assertNotNull($order->fresh()->completed_at);
|
|
|
|
|
+
|
|
|
|
|
+ // 测试最终状态不能再更改
|
|
|
|
|
+ $this->assertFalse($order->updateStatus(TransferStatus::FAILED));
|
|
|
|
|
+ $this->assertEquals(TransferStatus::COMPLETED, $order->fresh()->status);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试重复业务ID检查
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_duplicate_business_id_check()
|
|
|
|
|
+ {
|
|
|
|
|
+ $businessId = 'DUPLICATE_TEST_' . time();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建第一个订单
|
|
|
|
|
+ $data1 = [
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'business_id' => $businessId,
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '100.0000000000',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $result1 = TransferService::createTransferIn($data1);
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result1);
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试创建重复业务ID的订单
|
|
|
|
|
+ $data2 = [
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'business_id' => $businessId,
|
|
|
|
|
+ 'user_id' => 2,
|
|
|
|
|
+ 'amount' => '200.0000000000',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $this->expectException(\App\Module\Transfer\Exceptions\TransferException::class);
|
|
|
|
|
+ TransferService::createTransferIn($data2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试应用禁用状态检查
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_disabled_app_check()
|
|
|
|
|
+ {
|
|
|
|
|
+ // 禁用应用
|
|
|
|
|
+ $this->internalApp->update(['is_enabled' => false]);
|
|
|
|
|
+
|
|
|
|
|
+ $data = [
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'business_id' => 'DISABLED_APP_' . time(),
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'amount' => '100.0000000000',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $this->expectException(\App\Module\Transfer\Exceptions\TransferException::class);
|
|
|
|
|
+ TransferService::createTransferIn($data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试获取用户订单列表
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_get_user_orders()
|
|
|
|
|
+ {
|
|
|
|
|
+ $userId = 1;
|
|
|
|
|
+
|
|
|
|
|
+ // 创建多个订单
|
|
|
|
|
+ $orders = [];
|
|
|
|
|
+ for ($i = 1; $i <= 5; $i++) {
|
|
|
|
|
+ $order = TransferOrder::create([
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'out_order_id' => 'USER_ORDER_' . $i,
|
|
|
|
|
+ 'user_id' => $userId,
|
|
|
|
|
+ 'type' => $i % 2 == 0 ? TransferType::OUT : TransferType::IN,
|
|
|
|
|
+ 'status' => TransferStatus::COMPLETED,
|
|
|
|
|
+ 'out_amount' => '100.0000000000',
|
|
|
|
|
+ 'amount' => '100.0000000000',
|
|
|
|
|
+ 'exchange_rate' => 1.0000,
|
|
|
|
|
+ 'currency_id' => 2,
|
|
|
|
|
+ 'fund_id' => 2,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ $orders[] = $order;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取用户订单列表
|
|
|
|
|
+ $result = TransferService::getUserOrders($userId, 1, 10);
|
|
|
|
|
+
|
|
|
|
|
+ $this->assertIsArray($result);
|
|
|
|
|
+ $this->assertCount(5, $result);
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result[0]);
|
|
|
|
|
+
|
|
|
|
|
+ // 测试分页
|
|
|
|
|
+ $result = TransferService::getUserOrders($userId, 1, 3);
|
|
|
|
|
+ $this->assertCount(3, $result);
|
|
|
|
|
+
|
|
|
|
|
+ $result = TransferService::getUserOrders($userId, 2, 3);
|
|
|
|
|
+ $this->assertCount(2, $result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 测试订单查询
|
|
|
|
|
+ */
|
|
|
|
|
+ public function test_order_query()
|
|
|
|
|
+ {
|
|
|
|
|
+ // 创建测试订单
|
|
|
|
|
+ $order = TransferOrder::create([
|
|
|
|
|
+ 'transfer_app_id' => $this->internalApp->id,
|
|
|
|
|
+ 'out_order_id' => 'QUERY_TEST_' . time(),
|
|
|
|
|
+ 'user_id' => 1,
|
|
|
|
|
+ 'type' => TransferType::IN,
|
|
|
|
|
+ 'status' => TransferStatus::COMPLETED,
|
|
|
|
|
+ 'out_amount' => '100.0000000000',
|
|
|
|
|
+ 'amount' => '100.0000000000',
|
|
|
|
|
+ 'exchange_rate' => 1.0000,
|
|
|
|
|
+ 'currency_id' => 2,
|
|
|
|
|
+ 'fund_id' => 2,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 通过ID查询
|
|
|
|
|
+ $result = TransferService::getOrderDetail($order->id);
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals($order->id, $result->id);
|
|
|
|
|
+
|
|
|
|
|
+ // 通过业务ID查询
|
|
|
|
|
+ $result = TransferService::getOrderByBusinessId($order->out_order_id, $this->internalApp->out_id);
|
|
|
|
|
+ $this->assertInstanceOf(\App\Module\Transfer\Dtos\TransferOrderDto::class, $result);
|
|
|
|
|
+ $this->assertEquals($order->id, $result->id);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|