TransferApiTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. namespace App\Module\Transfer\Tests\Feature;
  3. use App\Module\Transfer\Models\TransferApp;
  4. use App\Module\Transfer\Models\TransferOrder;
  5. use App\Module\Transfer\Enums\TransferStatus;
  6. use App\Module\Transfer\Enums\TransferType;
  7. use App\Module\OpenAPI\Models\OpenApiApp;
  8. use Tests\TestCase;
  9. use Illuminate\Foundation\Testing\RefreshDatabase;
  10. use Illuminate\Foundation\Testing\WithFaker;
  11. /**
  12. * 划转API功能测试
  13. */
  14. class TransferApiTest extends TestCase
  15. {
  16. use RefreshDatabase, WithFaker;
  17. /**
  18. * 测试应用
  19. */
  20. protected TransferApp $testApp;
  21. /**
  22. * OpenAPI应用
  23. */
  24. protected OpenApiApp $apiApp;
  25. /**
  26. * 设置测试环境
  27. */
  28. protected function setUp(): void
  29. {
  30. parent::setUp();
  31. // 创建测试应用
  32. $this->testApp = TransferApp::create([
  33. 'keyname' => 'test_app',
  34. 'title' => '测试应用',
  35. 'description' => '用于API测试的应用',
  36. 'out_id' => 1001,
  37. 'currency_id' => 1,
  38. 'fund_id' => 1,
  39. 'exchange_rate' => 1.0000,
  40. 'is_enabled' => true,
  41. ]);
  42. // 创建OpenAPI应用(如果存在OpenAPI模块)
  43. if (class_exists(OpenApiApp::class)) {
  44. $this->apiApp = OpenApiApp::create([
  45. 'name' => 'Test API App',
  46. 'app_id' => 'test_app_001',
  47. 'app_secret' => 'test_secret_123',
  48. 'scopes' => ['TRANSFER_IN', 'TRANSFER_OUT', 'TRANSFER_QUERY'],
  49. 'is_enabled' => true,
  50. ]);
  51. }
  52. }
  53. /**
  54. * 测试转入API
  55. */
  56. public function testTransferInApi(): void
  57. {
  58. $data = [
  59. 'business_id' => 'api_test_in_' . time(),
  60. 'user_id' => 1001,
  61. 'amount' => '100.50',
  62. 'out_user_id' => 'ext_user_123',
  63. 'remark' => 'API测试转入',
  64. 'callback_data' => ['test' => 'data'],
  65. ];
  66. $response = $this->postJson('/api/transfer/in', $data, $this->getApiHeaders());
  67. $response->assertStatus(200)
  68. ->assertJsonStructure([
  69. 'code',
  70. 'message',
  71. 'data' => [
  72. 'order_id',
  73. 'business_id',
  74. 'amount',
  75. 'internal_amount',
  76. 'exchange_rate',
  77. 'status',
  78. 'status_text',
  79. 'created_at'
  80. ]
  81. ]);
  82. // 验证数据库中的记录
  83. $this->assertDatabaseHas('kku_transfer_orders', [
  84. 'out_order_id' => $data['business_id'],
  85. 'user_id' => $data['user_id'],
  86. 'type' => TransferType::IN->value,
  87. 'status' => TransferStatus::CREATED->value,
  88. ]);
  89. }
  90. /**
  91. * 测试转出API
  92. */
  93. public function testTransferOutApi(): void
  94. {
  95. $data = [
  96. 'business_id' => 'api_test_out_' . time(),
  97. 'user_id' => 1001,
  98. 'amount' => '50.25',
  99. 'out_user_id' => 'ext_user_456',
  100. 'remark' => 'API测试转出',
  101. ];
  102. $response = $this->postJson('/api/transfer/out', $data, $this->getApiHeaders());
  103. $response->assertStatus(200)
  104. ->assertJsonStructure([
  105. 'code',
  106. 'message',
  107. 'data' => [
  108. 'order_id',
  109. 'business_id',
  110. 'amount',
  111. 'out_amount',
  112. 'exchange_rate',
  113. 'status',
  114. 'status_text',
  115. 'created_at'
  116. ]
  117. ]);
  118. // 验证数据库中的记录
  119. $this->assertDatabaseHas('kku_transfer_orders', [
  120. 'out_order_id' => $data['business_id'],
  121. 'user_id' => $data['user_id'],
  122. 'type' => TransferType::OUT->value,
  123. ]);
  124. }
  125. /**
  126. * 测试查询API - 通过业务ID查询
  127. */
  128. public function testTransferQueryByBusinessId(): void
  129. {
  130. // 先创建一个订单
  131. $order = TransferOrder::create([
  132. 'transfer_app_id' => $this->testApp->id,
  133. 'out_id' => $this->testApp->out_id,
  134. 'out_order_id' => 'query_test_' . time(),
  135. 'user_id' => 1001,
  136. 'currency_id' => 1,
  137. 'fund_id' => 1,
  138. 'type' => TransferType::IN,
  139. 'status' => TransferStatus::COMPLETED,
  140. 'out_amount' => 100,
  141. 'amount' => 100,
  142. 'exchange_rate' => 1.0000,
  143. ]);
  144. $response = $this->getJson('/api/transfer/order?business_id=' . $order->out_order_id, $this->getApiHeaders());
  145. $response->assertStatus(200)
  146. ->assertJsonStructure([
  147. 'code',
  148. 'message',
  149. 'data' => [
  150. 'order_id',
  151. 'business_id',
  152. 'type',
  153. 'type_text',
  154. 'status',
  155. 'status_text',
  156. 'amount',
  157. 'out_amount',
  158. 'exchange_rate',
  159. 'user_id',
  160. 'created_at',
  161. ]
  162. ]);
  163. $responseData = $response->json('data');
  164. $this->assertEquals($order->id, $responseData['order_id']);
  165. $this->assertEquals($order->out_order_id, $responseData['business_id']);
  166. }
  167. /**
  168. * 测试查询API - 通过订单ID查询
  169. */
  170. public function testTransferQueryByOrderId(): void
  171. {
  172. // 先创建一个订单
  173. $order = TransferOrder::create([
  174. 'transfer_app_id' => $this->testApp->id,
  175. 'out_id' => $this->testApp->out_id,
  176. 'out_order_id' => 'query_order_test_' . time(),
  177. 'user_id' => 1001,
  178. 'currency_id' => 1,
  179. 'fund_id' => 1,
  180. 'type' => TransferType::OUT,
  181. 'status' => TransferStatus::PROCESSING,
  182. 'out_amount' => 200,
  183. 'amount' => 200,
  184. 'exchange_rate' => 1.0000,
  185. ]);
  186. $response = $this->getJson('/api/transfer/order?order_id=' . $order->id, $this->getApiHeaders());
  187. $response->assertStatus(200);
  188. $responseData = $response->json('data');
  189. $this->assertEquals($order->id, $responseData['order_id']);
  190. $this->assertEquals(TransferType::OUT->value, $responseData['type']);
  191. $this->assertEquals(TransferStatus::PROCESSING->value, $responseData['status']);
  192. }
  193. /**
  194. * 测试API验证 - 缺少必填字段
  195. */
  196. public function testApiValidationMissingFields(): void
  197. {
  198. $data = [
  199. // 缺少 business_id
  200. 'user_id' => 1001,
  201. 'amount' => '100.00',
  202. ];
  203. $response = $this->postJson('/api/transfer/in', $data, $this->getApiHeaders());
  204. $response->assertStatus(422)
  205. ->assertJsonStructure([
  206. 'code',
  207. 'message',
  208. 'data' => [
  209. 'business_id'
  210. ]
  211. ]);
  212. }
  213. /**
  214. * 测试API验证 - 无效金额
  215. */
  216. public function testApiValidationInvalidAmount(): void
  217. {
  218. $data = [
  219. 'business_id' => 'invalid_amount_test_' . time(),
  220. 'user_id' => 1001,
  221. 'amount' => 'invalid_amount',
  222. ];
  223. $response = $this->postJson('/api/transfer/in', $data, $this->getApiHeaders());
  224. $response->assertStatus(422)
  225. ->assertJsonStructure([
  226. 'code',
  227. 'message',
  228. 'data' => [
  229. 'amount'
  230. ]
  231. ]);
  232. }
  233. /**
  234. * 测试API权限验证
  235. */
  236. public function testApiPermissionValidation(): void
  237. {
  238. $data = [
  239. 'business_id' => 'permission_test_' . time(),
  240. 'user_id' => 1001,
  241. 'amount' => '100.00',
  242. ];
  243. // 不提供认证头
  244. $response = $this->postJson('/api/transfer/in', $data);
  245. $response->assertStatus(401);
  246. }
  247. /**
  248. * 测试重复业务ID
  249. */
  250. public function testDuplicateBusinessId(): void
  251. {
  252. $businessId = 'duplicate_api_test_' . time();
  253. $data = [
  254. 'business_id' => $businessId,
  255. 'user_id' => 1001,
  256. 'amount' => '100.00',
  257. ];
  258. // 第一次请求应该成功
  259. $response1 = $this->postJson('/api/transfer/in', $data, $this->getApiHeaders());
  260. $response1->assertStatus(200);
  261. // 第二次请求应该失败
  262. $response2 = $this->postJson('/api/transfer/in', $data, $this->getApiHeaders());
  263. $response2->assertStatus(400)
  264. ->assertJsonPath('message', '业务订单ID已存在');
  265. }
  266. /**
  267. * 测试查询不存在的订单
  268. */
  269. public function testQueryNonExistentOrder(): void
  270. {
  271. $response = $this->getJson('/api/transfer/order?business_id=non_existent_order', $this->getApiHeaders());
  272. $response->assertStatus(404)
  273. ->assertJsonPath('message', '订单不存在');
  274. }
  275. /**
  276. * 测试汇率转换
  277. */
  278. public function testExchangeRateInApi(): void
  279. {
  280. // 设置汇率为2.0
  281. $this->testApp->update(['exchange_rate' => 2.0000]);
  282. $data = [
  283. 'business_id' => 'rate_test_' . time(),
  284. 'user_id' => 1001,
  285. 'amount' => '100.00',
  286. ];
  287. $response = $this->postJson('/api/transfer/out', $data, $this->getApiHeaders());
  288. $response->assertStatus(200);
  289. $responseData = $response->json('data');
  290. $this->assertEquals('100.00', $responseData['amount']); // 内部金额
  291. $this->assertEquals('200.00', $responseData['out_amount']); // 外部金额
  292. $this->assertEquals('2.0000', $responseData['exchange_rate']);
  293. }
  294. /**
  295. * 获取API请求头
  296. */
  297. protected function getApiHeaders(): array
  298. {
  299. if (!isset($this->apiApp)) {
  300. return ['Accept' => 'application/json'];
  301. }
  302. return [
  303. 'Accept' => 'application/json',
  304. 'Authorization' => 'Bearer ' . $this->generateApiToken(),
  305. 'X-App-Id' => $this->apiApp->app_id,
  306. ];
  307. }
  308. /**
  309. * 生成API令牌(简化版)
  310. */
  311. protected function generateApiToken(): string
  312. {
  313. return 'test_token_' . time();
  314. }
  315. /**
  316. * 清理测试环境
  317. */
  318. protected function tearDown(): void
  319. {
  320. parent::tearDown();
  321. }
  322. }