UsdtRechargeConfigTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. namespace Tests\Integration;
  3. use App\Http\Controllers\admin\Config as AdminConfig;
  4. use App\Http\Controllers\api\Wallet;
  5. use App\Services\UsdtRechargeConfigService;
  6. use Illuminate\Config\Repository;
  7. use Illuminate\Database\Capsule\Manager;
  8. use Illuminate\Database\Schema\Blueprint;
  9. use Illuminate\Foundation\Application;
  10. use Illuminate\Http\JsonResponse;
  11. use Illuminate\Http\Request;
  12. use Illuminate\Routing\Router;
  13. use Illuminate\Routing\UrlGenerator;
  14. use Illuminate\Support\Facades\DB;
  15. use Illuminate\Support\Facades\Facade;
  16. use Illuminate\Translation\ArrayLoader;
  17. use Illuminate\Translation\Translator;
  18. use Illuminate\Validation\Factory;
  19. use PHPUnit\Framework\TestCase;
  20. class UsdtRechargeConfigTest extends TestCase
  21. {
  22. private Application $app;
  23. private UsdtRechargeConfigService $service;
  24. private string $storage;
  25. private const TRC = 'T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb';
  26. private const ERC = '0x0123456789abcdef0123456789abcdef01234567';
  27. protected function setUp(): void
  28. {
  29. require_once dirname(__DIR__, 2) . '/app/Helpers/helpers.php';
  30. Facade::clearResolvedInstances();
  31. $this->app = new Application(dirname(__DIR__, 2));
  32. $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']]));
  33. Facade::setFacadeApplication($this->app);
  34. $db = new Manager($this->app);
  35. $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
  36. $db->setAsGlobal();
  37. $db->bootEloquent();
  38. $this->app->instance('db', $db->getDatabaseManager());
  39. $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh'));
  40. $this->app->instance('validator', new Factory($this->app['translator'], $this->app));
  41. $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
  42. public function json($data, $status = 200, $headers = [], $options = 0) {
  43. return new JsonResponse($data, $status, $headers, $options);
  44. }
  45. });
  46. Request::macro('validate', function (array $rules) {
  47. return app('validator')->make($this->all(), $rules)->validate();
  48. });
  49. $db->schema()->create('config', function (Blueprint $table) {
  50. $table->id();
  51. $table->string('field')->unique();
  52. $table->text('val');
  53. $table->string('remark')->nullable();
  54. $table->integer('group_id')->default(1);
  55. $table->timestamps();
  56. });
  57. $this->storage = sys_get_temp_dir() . '/bot28-usdt-' . bin2hex(random_bytes(6));
  58. $this->app->useStoragePath($this->storage);
  59. mkdir($this->storage . '/app/public/recharge', 0777, true);
  60. foreach ([self::TRC, self::ERC] as $address) {
  61. // Exercise the real scan path with cached QR files, without generating images or using a chain service.
  62. file_put_contents($this->storage . '/app/public/recharge/' . $address . '.png', 'cached-qr-fixture');
  63. }
  64. $request = Request::create('https://example.test/admin/config/usdt');
  65. $this->app->instance('request', $request);
  66. $router = new Router(new \Illuminate\Events\Dispatcher($this->app), $this->app);
  67. $this->app->instance('url', new UrlGenerator($router->getRoutes(), $request));
  68. $this->service = new UsdtRechargeConfigService();
  69. }
  70. protected function tearDown(): void
  71. {
  72. foreach ([self::TRC, self::ERC] as $address) {
  73. unlink($this->storage . '/app/public/recharge/' . $address . '.png');
  74. }
  75. foreach (['/app/public/recharge', '/app/public', '/app', ''] as $directory) rmdir($this->storage . $directory);
  76. DB::disconnect();
  77. Facade::clearResolvedInstances();
  78. parent::tearDown();
  79. }
  80. private function settings(array $changes = []): array
  81. {
  82. return array_replace(['receiving_type' => 2, 'receiving_address' => self::TRC, 'receiving_address_erc20' => self::ERC, 'public_chain' => ''], $changes);
  83. }
  84. private function request(string $path, string $method, array $params): void
  85. {
  86. $request = Request::create($path, $method, $params);
  87. $request->user = (object)['member_id' => 'member-1'];
  88. $this->app->instance('request', $request);
  89. }
  90. private function scan(string $type): array
  91. {
  92. $this->request('/api/wallet/scan', 'GET', ['type' => $type]);
  93. return (new Wallet())->scan()->getData(true);
  94. }
  95. public function test_admin_saves_and_reads_both_addresses_without_other_system_settings(): void
  96. {
  97. $this->assertSame($this->settings(['receiving_address' => '', 'receiving_address_erc20' => '']), $this->service->get());
  98. $this->request('/admin/config/usdt', 'POST', $this->settings(['receiving_address' => ' ' . self::TRC . ' ']));
  99. $response = (new AdminConfig())->saveUsdt($this->service);
  100. $this->assertSame(0, $response->getData(true)['code']);
  101. $this->assertSame($this->settings(), $response->getData(true)['data']);
  102. $this->assertStringContainsString('no-store', $response->headers->get('Cache-Control'));
  103. $this->assertSame($this->settings(), (new AdminConfig())->usdt($this->service)->getData(true)['data']);
  104. $this->service->save($this->settings());
  105. $this->assertSame(4, DB::table('config')->count());
  106. }
  107. public function test_scan_returns_selected_manually_configured_address_and_matching_qr(): void
  108. {
  109. $this->service->save($this->settings());
  110. foreach (['TrC20' => self::TRC, 'erc20' => self::ERC] as $type => $address) {
  111. $result = $this->scan($type);
  112. $this->assertSame(0, $result['code']);
  113. $this->assertSame([
  114. 'qrcode' => 'https://example.test/storage/recharge/' . $address . '.png',
  115. 'address' => $address, 'public_chain' => '', 'net' => strtoupper($type), 'coin' => 'USDT', 'receiving_type' => 2,
  116. ], $result['data']);
  117. }
  118. }
  119. public function test_public_chain_saves_independently_returns_in_scan_and_can_be_cleared(): void
  120. {
  121. $this->request('/admin/config/usdt', 'POST', $this->settings(['public_chain' => ' public-chain-address ']));
  122. $saved = (new AdminConfig())->saveUsdt($this->service)->getData(true);
  123. $this->assertSame(0, $saved['code']);
  124. $this->assertSame('public-chain-address', $saved['data']['public_chain']);
  125. foreach (['trc20', 'erc20'] as $type) {
  126. $this->assertSame('public-chain-address', $this->scan($type)['data']['public_chain']);
  127. }
  128. $withoutPublicChain = $this->settings();
  129. unset($withoutPublicChain['public_chain']);
  130. $this->service->save($withoutPublicChain);
  131. $this->assertSame('public-chain-address', $this->service->get()['public_chain']);
  132. $this->service->save($this->settings(['public_chain' => null]));
  133. $this->assertSame('', $this->scan('trc20')['data']['public_chain']);
  134. $this->assertSame(self::TRC, $this->scan('trc20')['data']['address']);
  135. }
  136. public function test_blank_or_missing_chain_address_does_not_generate_a_qr_or_use_other_chain(): void
  137. {
  138. $this->service->save($this->settings(['receiving_address_erc20' => '']));
  139. $this->assertSame(-1, $this->scan('erc20')['code']);
  140. DB::table('config')->where('field', 'receiving_address')->delete();
  141. $this->assertSame(-1, $this->scan('trc20')['code']);
  142. $this->assertCount(2, glob($this->storage . '/app/public/recharge/*'));
  143. }
  144. public function test_automatic_mode_rejects_erc20_instead_of_returning_a_tron_address(): void
  145. {
  146. $this->service->save($this->settings(['receiving_type' => 1]));
  147. $this->assertSame(-1, $this->scan('erc20')['code']);
  148. $this->assertSame(-1, $this->scan('bep20')['code']);
  149. }
  150. public function test_automatic_trc20_uses_member_wallet_even_without_platform_addresses(): void
  151. {
  152. $this->service->save($this->settings(['receiving_type' => 1, 'receiving_address' => '', 'receiving_address_erc20' => '']));
  153. DB::table('config')->where('field', '!=', 'receiving_type')->delete();
  154. DB::statement('CREATE TABLE bot_wallets (id INTEGER PRIMARY KEY,member_id TEXT,coin TEXT,net TEXT,address TEXT,available_balance TEXT,frozen_balance TEXT)');
  155. DB::table('wallets')->insert(['id' => 1, 'member_id' => 'member-1', 'coin' => 'USDT', 'net' => 'TRC20',
  156. 'address' => self::TRC, 'available_balance' => '0', 'frozen_balance' => '0']);
  157. $this->app->instance('cache', new \Illuminate\Cache\Repository(new \Illuminate\Cache\ArrayStore()));
  158. $result = $this->scan('trc20');
  159. $this->assertSame(0, $result['code']);
  160. $this->assertSame(self::TRC, $result['data']['address']);
  161. $this->assertSame(1, $result['data']['receiving_type']);
  162. $this->assertArrayHasKey('member-1', $this->app['cache']->get('usdt_recharge_pending_sync_members'));
  163. }
  164. public function test_invalid_settings_do_not_partially_overwrite_saved_addresses(): void
  165. {
  166. $this->service->save($this->settings());
  167. foreach ([['receiving_type' => 3], ['receiving_address' => self::ERC],
  168. ['receiving_address_erc20' => self::TRC], ['receiving_address' => '../invalid'],
  169. ['receiving_address' => '', 'receiving_address_erc20' => ''],
  170. ['public_chain' => ['invalid']], ['public_chain' => str_repeat('a', 2049)]] as $change) {
  171. $this->request('/admin/config/usdt', 'POST', $this->settings($change));
  172. $this->assertSame(-3, (new AdminConfig())->saveUsdt($this->service)->getData(true)['code']);
  173. $this->assertSame($this->settings(), $this->service->get());
  174. }
  175. }
  176. public function test_existing_config_save_can_include_erc20_and_omission_preserves_it(): void
  177. {
  178. $this->service->save($this->settings());
  179. $params = $this->settings(['receiving_address_erc20' => '0x' . str_repeat('a', 40), 'public_chain' => 'public-chain-address']) + [
  180. 'base_score' => [1], 'brokerage' => '0.1', 'service_charge' => 1, 'service_account' => 'support',
  181. ];
  182. $this->request('/admin/config/set', 'POST', $params);
  183. $this->assertSame(0, (new AdminConfig())->set()->getData(true)['code']);
  184. unset($params['receiving_address_erc20'], $params['public_chain']);
  185. $this->request('/admin/config/set', 'POST', $params);
  186. $this->assertSame(0, (new AdminConfig())->set()->getData(true)['code']);
  187. $this->assertSame('0x' . str_repeat('a', 40), $this->service->get()['receiving_address_erc20']);
  188. $this->assertSame('public-chain-address', $this->service->get()['public_chain']);
  189. }
  190. public function test_routes_keep_admin_auth_and_existing_configuration_permissions(): void
  191. {
  192. $router = new Router(new \Illuminate\Events\Dispatcher($this->app), $this->app);
  193. $this->app->instance('router', $router);
  194. $router->prefix('admin')->group(dirname(__DIR__, 2) . '/routes/admin.php');
  195. foreach (['GET' => 'admin/config/getAll', 'POST' => 'admin/config/set'] as $method => $permission) {
  196. $route = $router->getRoutes()->match(Request::create('/admin/config/usdt', $method));
  197. $this->assertContains('admin.jwt', $route->gatherMiddleware());
  198. $this->assertContains('check.button.uri', $route->gatherMiddleware());
  199. $this->assertSame($permission, $route->defaults['permission_uri']);
  200. }
  201. }
  202. }