Merge origin/main; keep intercept env keys in migrate skill.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
hashbro
2026-10-12 04:19:56 +08:00
11 changed files with 848 additions and 321 deletions
+6 -3
View File
@@ -12,6 +12,7 @@ use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
use App\Services\DashboardStatsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
@@ -163,7 +164,7 @@ class DailyReportTest extends TestCase
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
\Illuminate\Support\Facades\DB::table('devices')->where('id', $device->id)->update([
DB::table('devices')->where('id', $device->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
@@ -172,7 +173,7 @@ class DailyReportTest extends TestCase
'address' => 'THistDailyAddr1111111111111111111',
'chain_type' => 'TRON',
]);
\Illuminate\Support\Facades\DB::table('wallet_addresses')->where('id', $addr->id)->update([
DB::table('wallet_addresses')->where('id', $addr->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
@@ -182,7 +183,7 @@ class DailyReportTest extends TestCase
]);
$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([
DB::table('wallet_mnemonics')->where('id', $mnemonic->id)->update([
'created_at' => $yesterday->toDateTimeString(),
'updated_at' => $yesterday->toDateTimeString(),
]);
@@ -198,6 +199,8 @@ class DailyReportTest extends TestCase
$this->assertNotNull($row);
$this->assertSame(1, $row->uv);
$this->assertSame(1, $row->effective_uv);
$this->assertSame(1, $row->pv);
$this->assertSame(1, $row->effective_pv);
$this->assertSame(1, $row->devices);
$this->assertSame(1, $row->wallet_devices);
$this->assertSame(1, $row->address_count);
+243
View File
@@ -0,0 +1,243 @@
<?php
namespace Tests\Feature;
use App\Models\Admin;
use App\Models\DailyStat;
use App\Models\Device;
use App\Models\PageVisit;
use App\Services\DashboardStatsService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class DashboardStatsTest extends TestCase
{
use RefreshDatabase;
private const CHANNEL = 'ffffffffffffffffffffffffffffffff';
protected function setUp(): void
{
parent::setUp();
config([
'coruna.panel.admin_hosts' => [],
'coruna.panel.agent_hosts' => [],
]);
Cache::flush();
}
#[Test]
public function collect_uses_daily_stats_when_present(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
Device::query()->create([
'device_id' => 'live-dev',
'channel_id' => self::CHANNEL,
'has_wallet' => Device::WALLET_YES,
]);
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 99,
'uv' => 88,
'effective_pv' => 77,
'effective_uv' => 66,
'devices' => 5,
'wallet_devices' => 3,
'address_count' => 4,
'mnemonic_count' => 2,
'computed_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect(['range' => 'today']);
$this->assertSame(99, $stats['pv']);
$this->assertSame(88, $stats['uv']);
$this->assertSame(77, $stats['effective_pv']);
$this->assertSame(66, $stats['effective_uv']);
$this->assertSame(5, $stats['total']);
$this->assertSame(3, $stats['wallet_count']);
$this->assertSame(4, $stats['address_count']);
$this->assertSame(2, $stats['mnemonic_count']);
}
#[Test]
public function collect_falls_back_to_live_visits_when_rollup_missing(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'live-uv',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect(['range' => 'today']);
$this->assertSame(2, $stats['pv']);
$this->assertSame(1, $stats['uv']);
$this->assertSame(2, $stats['effective_pv']);
$this->assertSame(1, $stats['effective_uv']);
}
#[Test]
public function collect_scans_live_when_channel_filter_set(): void
{
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' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'client_uid' => 'other',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 99,
'uv' => 88,
'effective_pv' => 77,
'effective_uv' => 66,
'devices' => 5,
'wallet_devices' => 3,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$stats = app(DashboardStatsService::class)->collect([
'range' => 'today',
'channel_id' => self::CHANNEL,
'channel_exact' => true,
]);
$this->assertSame(1, $stats['pv']);
$this->assertSame(1, $stats['uv']);
}
#[Test]
public function collect_caches_result(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 10,
'uv' => 9,
'effective_pv' => 8,
'effective_uv' => 7,
'devices' => 1,
'wallet_devices' => 0,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$svc = app(DashboardStatsService::class);
$first = $svc->collect(['range' => 'today']);
DailyStat::query()->update(['pv' => 50, 'uv' => 40]);
$second = $svc->collect(['range' => 'today']);
$this->assertSame(10, $first['pv']);
$this->assertSame(10, $second['pv']);
$this->assertSame(9, $second['uv']);
Cache::flush();
$third = $svc->collect(['range' => 'today']);
$this->assertSame(50, $third['pv']);
$this->assertSame(40, $third['uv']);
}
#[Test]
public function collect_sums_daily_rows_across_range(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 10,
'uv' => 4,
'effective_pv' => 3,
'effective_uv' => 2,
'devices' => 1,
'wallet_devices' => 1,
'address_count' => 1,
'mnemonic_count' => 1,
'computed_at' => now(),
]);
DailyStat::query()->create([
'stat_date' => now()->subDay()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 7,
'uv' => 3,
'effective_pv' => 2,
'effective_uv' => 1,
'devices' => 2,
'wallet_devices' => 0,
'address_count' => 3,
'mnemonic_count' => 4,
'computed_at' => now()->subDay(),
]);
$stats = app(DashboardStatsService::class)->collect([
'date_from' => now()->subDay()->toDateString(),
'date_to' => now()->toDateString(),
]);
$this->assertSame(17, $stats['pv']);
$this->assertSame(7, $stats['uv']);
$this->assertSame(5, $stats['effective_pv']);
$this->assertSame(3, $stats['effective_uv']);
$this->assertSame(3, $stats['total']);
$this->assertSame(1, $stats['wallet_count']);
$this->assertSame(4, $stats['address_count']);
$this->assertSame(5, $stats['mnemonic_count']);
}
#[Test]
public function admin_dashboard_data_uses_rollup(): void
{
DailyStat::query()->create([
'stat_date' => now()->toDateString(),
'scope_key' => DailyStat::SCOPE_ALL,
'pv' => 12,
'uv' => 6,
'effective_pv' => 4,
'effective_uv' => 3,
'devices' => 2,
'wallet_devices' => 1,
'address_count' => 0,
'mnemonic_count' => 0,
'computed_at' => now(),
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$this->actingAs($admin, 'admin')
->getJson(route('admin.dashboard.data', ['range' => 'today']))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.pv', 12)
->assertJsonPath('data.uv', 6)
->assertJsonPath('data.total', 2);
}
}
+44
View File
@@ -698,4 +698,48 @@ class PageVisitTest extends TestCase
->assertJsonPath('data.1.stage', 'pe')
->assertJsonPath('data.1.stage_label', '权限提升');
}
#[Test]
public function visits_data_defaults_to_today(): void
{
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'today-uid',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now(),
]);
PageVisit::query()->create([
'channel_id' => self::CHANNEL,
'client_uid' => 'old-uid',
'os' => 'iOS',
'os_version' => '16.6',
'browser' => 'Safari',
'created_at' => now()->subDays(2),
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$payload = $this->actingAs($admin, 'admin')
->getJson(route('admin.visits.data'))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('count', 1)
->json('data');
$this->assertCount(1, $payload);
$this->assertSame('today-uid', $payload[0]['client_uid']);
}
#[Test]
public function visits_page_defaults_range_to_today(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$this->actingAs($admin, 'admin')
->get(route('admin.visits.index'))
->assertOk()
->assertSee('value="today" selected', false)
->assertSee("range: 'today'", false)
->assertDontSee("range: '30d'", false);
}
}