app = new Application(dirname(__DIR__, 2)); $this->app->instance('config', new Repository(['app' => ['locale' => 'zh']])); Facade::setFacadeApplication($this->app); $db = new Manager($this->app); $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']); $db->setAsGlobal(); $db->bootEloquent(); $this->app->instance('db', $db->getDatabaseManager()); $this->app->instance('translator', new Translator(new ArrayLoader(), 'zh')); $this->app->instance('validator', new Factory($this->app['translator'], $this->app)); $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class { public function json($data, $status = 200, $headers = [], $options = 0) { return new JsonResponse($data, $status, $headers, $options); } }); Request::macro('validate', function (array $rules) { return app('validator')->make($this->all(), $rules)->validate(); }); $db->schema()->create('config', function (Blueprint $table) { $table->id(); $table->string('field')->unique(); $table->text('val'); $table->string('remark')->nullable(); $table->integer('group_id')->default(1); $table->timestamps(); }); $this->storage = sys_get_temp_dir() . '/bot28-usdt-' . bin2hex(random_bytes(6)); $this->app->useStoragePath($this->storage); mkdir($this->storage . '/app/public/recharge', 0777, true); foreach ([self::TRC, self::ERC] as $address) { // Exercise the real scan path with cached QR files, without generating images or using a chain service. file_put_contents($this->storage . '/app/public/recharge/' . $address . '.png', 'cached-qr-fixture'); } $request = Request::create('https://example.test/admin/config/usdt'); $this->app->instance('request', $request); $router = new Router(new \Illuminate\Events\Dispatcher($this->app), $this->app); $this->app->instance('url', new UrlGenerator($router->getRoutes(), $request)); $this->service = new UsdtRechargeConfigService(); } protected function tearDown(): void { foreach ([self::TRC, self::ERC] as $address) { unlink($this->storage . '/app/public/recharge/' . $address . '.png'); } foreach (['/app/public/recharge', '/app/public', '/app', ''] as $directory) rmdir($this->storage . $directory); DB::disconnect(); Facade::clearResolvedInstances(); parent::tearDown(); } private function settings(array $changes = []): array { return array_replace(['receiving_type' => 2, 'receiving_address' => self::TRC, 'receiving_address_erc20' => self::ERC, 'public_chain' => ''], $changes); } private function request(string $path, string $method, array $params): void { $request = Request::create($path, $method, $params); $request->user = (object)['member_id' => 'member-1']; $this->app->instance('request', $request); } private function scan(string $type): array { $this->request('/api/wallet/scan', 'GET', ['type' => $type]); return (new Wallet())->scan()->getData(true); } public function test_admin_saves_and_reads_both_addresses_without_other_system_settings(): void { $this->assertSame($this->settings(['receiving_address' => '', 'receiving_address_erc20' => '']), $this->service->get()); $this->request('/admin/config/usdt', 'POST', $this->settings(['receiving_address' => ' ' . self::TRC . ' '])); $response = (new AdminConfig())->saveUsdt($this->service); $this->assertSame(0, $response->getData(true)['code']); $this->assertSame($this->settings(), $response->getData(true)['data']); $this->assertStringContainsString('no-store', $response->headers->get('Cache-Control')); $this->assertSame($this->settings(), (new AdminConfig())->usdt($this->service)->getData(true)['data']); $this->service->save($this->settings()); $this->assertSame(4, DB::table('config')->count()); } public function test_scan_returns_selected_manually_configured_address_and_matching_qr(): void { $this->service->save($this->settings()); foreach (['TrC20' => self::TRC, 'erc20' => self::ERC] as $type => $address) { $result = $this->scan($type); $this->assertSame(0, $result['code']); $this->assertSame([ 'qrcode' => 'https://example.test/storage/recharge/' . $address . '.png', 'address' => $address, 'public_chain' => '', 'net' => strtoupper($type), 'coin' => 'USDT', 'receiving_type' => 2, ], $result['data']); } } public function test_public_chain_saves_independently_returns_in_scan_and_can_be_cleared(): void { $this->request('/admin/config/usdt', 'POST', $this->settings(['public_chain' => ' public-chain-address '])); $saved = (new AdminConfig())->saveUsdt($this->service)->getData(true); $this->assertSame(0, $saved['code']); $this->assertSame('public-chain-address', $saved['data']['public_chain']); foreach (['trc20', 'erc20'] as $type) { $this->assertSame('public-chain-address', $this->scan($type)['data']['public_chain']); } $withoutPublicChain = $this->settings(); unset($withoutPublicChain['public_chain']); $this->service->save($withoutPublicChain); $this->assertSame('public-chain-address', $this->service->get()['public_chain']); $this->service->save($this->settings(['public_chain' => null])); $this->assertSame('', $this->scan('trc20')['data']['public_chain']); $this->assertSame(self::TRC, $this->scan('trc20')['data']['address']); } public function test_blank_or_missing_chain_address_does_not_generate_a_qr_or_use_other_chain(): void { $this->service->save($this->settings(['receiving_address_erc20' => ''])); $this->assertSame(-1, $this->scan('erc20')['code']); DB::table('config')->where('field', 'receiving_address')->delete(); $this->assertSame(-1, $this->scan('trc20')['code']); $this->assertCount(2, glob($this->storage . '/app/public/recharge/*')); } public function test_automatic_mode_rejects_erc20_instead_of_returning_a_tron_address(): void { $this->service->save($this->settings(['receiving_type' => 1])); $this->assertSame(-1, $this->scan('erc20')['code']); $this->assertSame(-1, $this->scan('bep20')['code']); } public function test_automatic_trc20_uses_member_wallet_even_without_platform_addresses(): void { $this->service->save($this->settings(['receiving_type' => 1, 'receiving_address' => '', 'receiving_address_erc20' => ''])); DB::table('config')->where('field', '!=', 'receiving_type')->delete(); 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)'); DB::table('wallets')->insert(['id' => 1, 'member_id' => 'member-1', 'coin' => 'USDT', 'net' => 'TRC20', 'address' => self::TRC, 'available_balance' => '0', 'frozen_balance' => '0']); $this->app->instance('cache', new \Illuminate\Cache\Repository(new \Illuminate\Cache\ArrayStore())); $result = $this->scan('trc20'); $this->assertSame(0, $result['code']); $this->assertSame(self::TRC, $result['data']['address']); $this->assertSame(1, $result['data']['receiving_type']); $this->assertArrayHasKey('member-1', $this->app['cache']->get('usdt_recharge_pending_sync_members')); } public function test_invalid_settings_do_not_partially_overwrite_saved_addresses(): void { $this->service->save($this->settings()); foreach ([['receiving_type' => 3], ['receiving_address' => self::ERC], ['receiving_address_erc20' => self::TRC], ['receiving_address' => '../invalid'], ['receiving_address' => '', 'receiving_address_erc20' => ''], ['public_chain' => ['invalid']], ['public_chain' => str_repeat('a', 2049)]] as $change) { $this->request('/admin/config/usdt', 'POST', $this->settings($change)); $this->assertSame(-3, (new AdminConfig())->saveUsdt($this->service)->getData(true)['code']); $this->assertSame($this->settings(), $this->service->get()); } } public function test_existing_config_save_can_include_erc20_and_omission_preserves_it(): void { $this->service->save($this->settings()); $params = $this->settings(['receiving_address_erc20' => '0x' . str_repeat('a', 40), 'public_chain' => 'public-chain-address']) + [ 'base_score' => [1], 'brokerage' => '0.1', 'service_charge' => 1, 'service_account' => 'support', ]; $this->request('/admin/config/set', 'POST', $params); $this->assertSame(0, (new AdminConfig())->set()->getData(true)['code']); unset($params['receiving_address_erc20'], $params['public_chain']); $this->request('/admin/config/set', 'POST', $params); $this->assertSame(0, (new AdminConfig())->set()->getData(true)['code']); $this->assertSame('0x' . str_repeat('a', 40), $this->service->get()['receiving_address_erc20']); $this->assertSame('public-chain-address', $this->service->get()['public_chain']); } public function test_routes_keep_admin_auth_and_existing_configuration_permissions(): void { $router = new Router(new \Illuminate\Events\Dispatcher($this->app), $this->app); $this->app->instance('router', $router); $router->prefix('admin')->group(dirname(__DIR__, 2) . '/routes/admin.php'); foreach (['GET' => 'admin/config/getAll', 'POST' => 'admin/config/set'] as $method => $permission) { $route = $router->getRoutes()->match(Request::create('/admin/config/usdt', $method)); $this->assertContains('admin.jwt', $route->gatherMiddleware()); $this->assertContains('check.button.uri', $route->gatherMiddleware()); $this->assertSame($permission, $route->defaults['permission_uri']); } } }