702 lines
25 KiB
PHP
702 lines
25 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Jobs\RecordPageHit;
|
|
use App\Models\Admin;
|
|
use App\Models\DsChainLog;
|
|
use App\Models\PageVisit;
|
|
use App\Support\UserAgentParser;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
class PageVisitTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private const CHANNEL = 'fe398bc1add87202c04c27293a032634';
|
|
|
|
#[Test]
|
|
public function hit_records_new_builder_version_channel(): void
|
|
{
|
|
Cache::flush();
|
|
$channel = '3.1.07';
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => $channel,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
'p' => '/channel/'.$channel.'/weifile/weifile.html',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame($channel, $row->channel_id);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_records_visit_with_parsed_ua(): void
|
|
{
|
|
Cache::flush();
|
|
$ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1';
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
'p' => '/web/'.self::CHANNEL.'/support.html',
|
|
'r' => 'https://example.com/from',
|
|
], [], [], [
|
|
'HTTP_USER_AGENT' => $ua,
|
|
])->assertOk()
|
|
->assertHeader('Content-Type', 'image/gif');
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame(self::CHANNEL, $row->channel_id);
|
|
$this->assertSame(PageVisit::CHAIN_CORUNA, $row->chain);
|
|
$this->assertSame('Coruna', PageVisit::chainLabel((int) $row->chain));
|
|
$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);
|
|
$this->assertSame('16.6', $row->browser_version);
|
|
$this->assertSame($ua, $row->user_agent);
|
|
$this->assertSame('https://example.com/from', $row->referer);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_marks_darksword_only_for_allowlisted_ios(): void
|
|
{
|
|
$cases = [
|
|
['17_2_1', PageVisit::CHAIN_CORUNA, '17.2.1'],
|
|
['17_2_2', PageVisit::CHAIN_CORUNA, '17.2.2'],
|
|
['18_0', PageVisit::CHAIN_CORUNA, '18.0'],
|
|
['18_1_1', PageVisit::CHAIN_DARKSWORD, '18.1.1'],
|
|
['18_4', PageVisit::CHAIN_DARKSWORD, '18.4'],
|
|
['18_4_1', PageVisit::CHAIN_DARKSWORD, '18.4.1'],
|
|
['18_5', PageVisit::CHAIN_DARKSWORD, '18.5'],
|
|
['18_5_1', PageVisit::CHAIN_CORUNA, '18.5.1'],
|
|
['18_6', PageVisit::CHAIN_DARKSWORD, '18.6'],
|
|
['18_6_1', PageVisit::CHAIN_DARKSWORD, '18.6.1'],
|
|
['18_6_2', PageVisit::CHAIN_DARKSWORD, '18.6.2'],
|
|
['18_6_3', PageVisit::CHAIN_CORUNA, '18.6.3'],
|
|
['18_7', PageVisit::CHAIN_DARKSWORD, '18.7'],
|
|
['18_7_1', PageVisit::CHAIN_DARKSWORD, '18.7.1'],
|
|
['18_7_2', PageVisit::CHAIN_DARKSWORD, '18.7.2'],
|
|
['18_7_3', PageVisit::CHAIN_CORUNA, '18.7.3'],
|
|
];
|
|
foreach ($cases as [$token, $chain, $osVersion]) {
|
|
Cache::flush();
|
|
PageVisit::query()->delete();
|
|
$ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS '.$token.' like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/'.$osVersion.' Mobile/15E148 Safari/604.1';
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'HTTP_USER_AGENT' => $ua,
|
|
])->assertOk();
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row, $osVersion);
|
|
$this->assertSame($osVersion, $row->os_version, $osVersion);
|
|
$this->assertSame($chain, $row->chain, $osVersion);
|
|
}
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_uses_x_forwarded_for_when_proxies_trusted(): void
|
|
{
|
|
Cache::flush();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'REMOTE_ADDR' => '104.16.1.1',
|
|
'HTTP_X_FORWARDED_FOR' => '203.0.113.50',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame('203.0.113.50', $row->ip);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_prefers_cf_connecting_ip_over_x_forwarded_for(): void
|
|
{
|
|
Cache::flush();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'REMOTE_ADDR' => '104.16.1.1',
|
|
'HTTP_X_FORWARDED_FOR' => '1.2.3.4',
|
|
'HTTP_CF_CONNECTING_IP' => '203.0.113.88',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame('203.0.113.88', $row->ip);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_stores_cf_ipcountry(): void
|
|
{
|
|
Cache::flush();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'HTTP_CF_IPCOUNTRY' => 'cn',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame('CN', $row->country);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_ignores_invalid_cf_ipcountry(): void
|
|
{
|
|
Cache::flush();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'HTTP_CF_IPCOUNTRY' => 'China',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertNull($row->country);
|
|
}
|
|
|
|
#[Test]
|
|
public function ds_register_fills_country_on_merged_visit(): void
|
|
{
|
|
Cache::flush();
|
|
$channel = '3.1.07';
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => $channel,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
])->assertOk();
|
|
|
|
$this->assertNull(PageVisit::query()->first()?->country);
|
|
|
|
$this->postJson('/api/ds/device/register', [
|
|
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
|
'channelCode' => $channel,
|
|
'ios' => '18.5',
|
|
'chain' => 'darksword',
|
|
], [
|
|
'CF-IPCountry' => 'US',
|
|
])->assertOk();
|
|
|
|
$this->assertSame(1, PageVisit::query()->count());
|
|
$this->assertSame('US', PageVisit::query()->first()?->country);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_uses_referer_header_when_query_missing(): void
|
|
{
|
|
Cache::flush();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
], [], [], [
|
|
'HTTP_REFERER' => 'https://search.example/q=ios',
|
|
])->assertOk();
|
|
|
|
$row = PageVisit::query()->first();
|
|
$this->assertNotNull($row);
|
|
$this->assertSame('https://search.example/q=ios', $row->referer);
|
|
}
|
|
|
|
#[Test]
|
|
public function canonical_ios_version_strips_trailing_zero_patch(): void
|
|
{
|
|
$this->assertSame('18.5', PageVisit::canonicalIosVersion('18.5'));
|
|
$this->assertSame('18.5', PageVisit::canonicalIosVersion('18.5.0'));
|
|
$this->assertSame('18.6.1', PageVisit::canonicalIosVersion('18.6.1'));
|
|
$this->assertTrue(PageVisit::isDarkSwordIosVersionString('18.5.0'));
|
|
$this->assertFalse(PageVisit::isDarkSwordIosVersionString('18.5.1'));
|
|
}
|
|
|
|
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 statistic_and_ds_register_merge_into_one_visit(): void
|
|
{
|
|
Cache::flush();
|
|
$channel = '3.1.07';
|
|
$ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1';
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => $channel,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
'p' => '/channel/'.$channel.'/weifile/weifile.html',
|
|
], [], [], [
|
|
'HTTP_USER_AGENT' => $ua,
|
|
])->assertOk();
|
|
|
|
$this->postJson('/api/ds/device/register', [
|
|
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
|
'channelCode' => $channel,
|
|
'ios' => '18.5',
|
|
'chain' => 'darksword',
|
|
'user_agent' => $ua,
|
|
])->assertOk();
|
|
|
|
$this->assertSame(1, PageVisit::query()->count());
|
|
$row = PageVisit::query()->first();
|
|
$this->assertSame($channel, $row->channel_id);
|
|
$this->assertSame(PageVisit::CHAIN_DARKSWORD, $row->chain);
|
|
$this->assertSame('50624FE26CC4A0DF689EAEA117557C3E', $row->client_uid);
|
|
$this->assertSame('18.5', $row->os_version);
|
|
}
|
|
|
|
#[Test]
|
|
public function statistic_after_ds_register_does_not_add_a_second_visit(): void
|
|
{
|
|
Cache::flush();
|
|
$channel = '3.1.07';
|
|
|
|
$this->postJson('/api/ds/device/register', [
|
|
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
|
'channelCode' => $channel,
|
|
'ios' => '18.5',
|
|
'chain' => 'darksword',
|
|
])->assertOk();
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => $channel,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
])->assertOk();
|
|
|
|
$this->assertSame(1, PageVisit::query()->count());
|
|
$row = PageVisit::query()->first();
|
|
$this->assertSame(PageVisit::CHAIN_DARKSWORD, $row->chain);
|
|
$this->assertSame('50624FE26CC4A0DF689EAEA117557C3E', $row->client_uid);
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_debounces_duplicate_uid(): void
|
|
{
|
|
Cache::flush();
|
|
$params = [
|
|
'c' => self::CHANNEL,
|
|
'u' => 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee',
|
|
];
|
|
$this->get('/statistic/t?'.http_build_query($params))->assertOk();
|
|
$this->get('/statistic/t?'.http_build_query($params))->assertOk();
|
|
$this->assertSame(1, PageVisit::query()->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_rejects_bad_channel(): void
|
|
{
|
|
Cache::flush();
|
|
$this->get('/statistic/t?c=not-a-channel&u=11111111-2222-4333-8444-555555555555')->assertOk();
|
|
$this->assertSame(0, PageVisit::query()->count());
|
|
}
|
|
|
|
#[Test]
|
|
public function hit_queues_record_when_queue_is_not_sync(): void
|
|
{
|
|
Cache::flush();
|
|
Queue::fake();
|
|
config(['queue.default' => 'redis']);
|
|
|
|
$this->call('GET', '/statistic/t', [
|
|
'c' => self::CHANNEL,
|
|
'u' => '11111111-2222-4333-8444-555555555555',
|
|
])->assertOk();
|
|
|
|
$this->assertSame(0, PageVisit::query()->count());
|
|
Queue::assertPushed(RecordPageHit::class, function (RecordPageHit $job) {
|
|
return ($job->attrs['channel_id'] ?? null) === self::CHANNEL;
|
|
});
|
|
}
|
|
|
|
#[Test]
|
|
public function show_alias_serves_channel_details_without_store_cache(): void
|
|
{
|
|
$ver = '8.8.88';
|
|
$dir = public_path('channel/'.$ver.'/details');
|
|
File::ensureDirectoryExists($dir);
|
|
file_put_contents($dir.'/show.html', 'SHOW_ALIAS_BODY');
|
|
try {
|
|
$response = $this->get('/c/'.$ver.'/show.htm')
|
|
->assertOk()
|
|
->assertHeader('Content-Type', 'application/octet-stream')
|
|
->assertHeader('Cache-Control', 'max-age=60, public');
|
|
$this->assertSame('SHOW_ALIAS_BODY', $response->streamedContent());
|
|
} finally {
|
|
File::deleteDirectory(public_path('channel/'.$ver));
|
|
}
|
|
}
|
|
|
|
#[Test]
|
|
public function user_agent_parser_handles_chrome_ios(): void
|
|
{
|
|
$parsed = UserAgentParser::parse(
|
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/119.0.6045.109 Mobile/15E148 Safari/604.1'
|
|
);
|
|
$this->assertSame('iOS', $parsed['os']);
|
|
$this->assertSame('17.0', $parsed['os_version']);
|
|
$this->assertSame('Chrome', $parsed['browser']);
|
|
$this->assertSame('119.0.6045', $parsed['browser_version']);
|
|
}
|
|
|
|
#[Test]
|
|
public function user_agent_parser_keeps_ios_patch(): void
|
|
{
|
|
$parsed = UserAgentParser::parse(
|
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 15_8_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.7 Mobile/15E148 Safari/604.1'
|
|
);
|
|
$this->assertSame('iOS', $parsed['os']);
|
|
$this->assertSame('15.8.4', $parsed['os_version']);
|
|
$this->assertSame('Safari', $parsed['browser']);
|
|
$this->assertSame('15.6.7', $parsed['browser_version']);
|
|
}
|
|
|
|
#[Test]
|
|
public function user_agent_parser_maps_ios26_compat_ua(): void
|
|
{
|
|
$parsed = UserAgentParser::parse(
|
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.6 Mobile/15E148 Safari/604.1'
|
|
);
|
|
$this->assertSame('iOS', $parsed['os']);
|
|
$this->assertSame('26.6', $parsed['os_version']);
|
|
$this->assertSame('Safari', $parsed['browser']);
|
|
$this->assertSame('26.6', $parsed['browser_version']);
|
|
}
|
|
|
|
#[Test]
|
|
public function user_agent_parser_keeps_real_ios_18_7(): void
|
|
{
|
|
$parsed = UserAgentParser::parse(
|
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Mobile/15E148 Safari/604.1'
|
|
);
|
|
$this->assertSame('iOS', $parsed['os']);
|
|
$this->assertSame('18.7', $parsed['os_version']);
|
|
$this->assertSame('Safari', $parsed['browser']);
|
|
$this->assertSame('18.7', $parsed['browser_version']);
|
|
}
|
|
|
|
#[Test]
|
|
public function dashboard_includes_pv_uv(): void
|
|
{
|
|
Cache::flush();
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u1',
|
|
'os' => 'iOS',
|
|
'os_version' => '16.6',
|
|
'browser' => 'Safari',
|
|
'browser_version' => '16.6',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u1',
|
|
'os' => 'iOS',
|
|
'os_version' => '16.6',
|
|
'browser' => 'Safari',
|
|
'browser_version' => '16.6',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u2',
|
|
'os' => 'Android',
|
|
'os_version' => '13',
|
|
'browser' => 'Chrome',
|
|
'browser_version' => '120.0.0',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u3',
|
|
'os' => 'iOS',
|
|
'os_version' => '17.2.1',
|
|
'browser' => 'Safari',
|
|
'browser_version' => '17.2',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u4',
|
|
'os' => 'iOS',
|
|
'os_version' => '16.0',
|
|
'browser' => 'Chrome',
|
|
'browser_version' => '119.0',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'u5',
|
|
'os' => 'iOS',
|
|
'os_version' => '17.3',
|
|
'browser' => 'Safari',
|
|
'browser_version' => '17.3',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.dashboard.data', ['range' => 'today']))
|
|
->assertOk()
|
|
->assertJsonPath('data.pv', 6)
|
|
->assertJsonPath('data.uv', 5)
|
|
// effective: two 16.6 Safari (u1) + one 17.2.1 Safari (u3); exclude Android/Chrome/17.3
|
|
->assertJsonPath('data.effective_pv', 3)
|
|
->assertJsonPath('data.effective_uv', 2)
|
|
->assertJsonStructure(['data' => ['pv', 'uv', 'effective_pv', 'effective_uv', 'total', 'wallet_count', 'balances', 'mnemonic_count', 'address_count']]);
|
|
}
|
|
|
|
#[Test]
|
|
public function dashboard_effective_pv_uses_new_ios_bands(): void
|
|
{
|
|
Cache::flush();
|
|
$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],
|
|
['18.2', false],
|
|
['18.2.1', false],
|
|
['18.1.1', true],
|
|
['18.4', true],
|
|
['18.4.1', true],
|
|
['18.5', true],
|
|
['18.5.1', false],
|
|
['18.6', true],
|
|
['18.6.1', true],
|
|
['18.6.2', true],
|
|
['18.6.3', false],
|
|
['18.7', true],
|
|
['18.7.1', true],
|
|
['18.7.2', true],
|
|
['18.7.3', false],
|
|
];
|
|
foreach ($rows as $i => [$ver, $_]) {
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'band'.$i,
|
|
'os' => 'iOS',
|
|
'os_version' => $ver,
|
|
'browser' => 'Safari',
|
|
'created_at' => now(),
|
|
]);
|
|
}
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'chrome185',
|
|
'os' => 'iOS',
|
|
'os_version' => '18.5',
|
|
'browser' => 'Chrome',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.dashboard.data', ['range' => 'today']))
|
|
->assertOk()
|
|
->assertJsonPath('data.pv', count($rows) + 1)
|
|
->assertJsonPath('data.effective_pv', count(array_filter($rows, static fn ($r) => $r[1])));
|
|
}
|
|
|
|
#[Test]
|
|
public function visits_data_labels_chain(): void
|
|
{
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => '50624FE26CC4A0DF689EAEA117557C3E',
|
|
'chain' => PageVisit::CHAIN_DARKSWORD,
|
|
'os' => 'iOS',
|
|
'os_version' => '18.6',
|
|
'browser' => 'Safari',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.data', ['range' => 'today']))
|
|
->assertOk()
|
|
->assertJsonPath('data.0.chain', 1)
|
|
->assertJsonPath('data.0.chain_label', 'DarkSword')
|
|
->assertJsonPath('data.0.client_uid', '50624FE26CC4A0DF689EAEA117557C3E');
|
|
}
|
|
|
|
#[Test]
|
|
public function visits_data_labels_and_filters_country(): void
|
|
{
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'aaaaaaaaaaaaaaaa',
|
|
'chain' => PageVisit::CHAIN_CORUNA,
|
|
'os' => 'iOS',
|
|
'ip' => '1.2.3.4',
|
|
'country' => 'CN',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'bbbbbbbbbbbbbbbb',
|
|
'chain' => PageVisit::CHAIN_CORUNA,
|
|
'os' => 'iOS',
|
|
'ip' => '5.6.7.8',
|
|
'country' => 'US',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.data', ['range' => 'today']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.data', ['range' => 'today', 'country' => 'cn']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.country', 'CN')
|
|
->assertJsonPath('data.0.country_label', '中国');
|
|
}
|
|
|
|
#[Test]
|
|
public function visits_data_filters_country_by_chinese_name(): void
|
|
{
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'aaaaaaaaaaaaaaaa',
|
|
'chain' => PageVisit::CHAIN_CORUNA,
|
|
'os' => 'iOS',
|
|
'ip' => '1.2.3.4',
|
|
'country' => 'CN',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'bbbbbbbbbbbbbbbb',
|
|
'chain' => PageVisit::CHAIN_CORUNA,
|
|
'os' => 'iOS',
|
|
'ip' => '5.6.7.8',
|
|
'country' => 'US',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
|
|
// Chinese name resolves to the same ISO code as the stored value.
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.data', ['range' => 'today', 'country' => '中国']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.country', 'CN')
|
|
->assertJsonPath('data.0.country_label', '中国');
|
|
|
|
// Unrecognized text is ignored (no filter applied), consistent with
|
|
// how invalid ISO codes are treated.
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.data', ['range' => 'today', 'country' => '不存在的国家']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
}
|
|
|
|
#[Test]
|
|
public function visits_data_does_not_filter_chain_until_explicitly_chosen(): void
|
|
{
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => 'aaaaaaaaaaaaaaaa',
|
|
'chain' => PageVisit::CHAIN_CORUNA,
|
|
'os' => 'iOS',
|
|
'created_at' => now(),
|
|
]);
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => '50624FE26CC4A0DF689EAEA117557C3E',
|
|
'chain' => PageVisit::CHAIN_DARKSWORD,
|
|
'os' => 'iOS',
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin');
|
|
|
|
foreach (['', 'all'] as $chain) {
|
|
$this->getJson(route('admin.visits.data', ['range' => 'today', 'chain' => $chain]))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
}
|
|
|
|
$this->getJson(route('admin.visits.data', ['range' => 'today']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 2);
|
|
|
|
$this->getJson(route('admin.visits.data', ['range' => 'today', 'chain' => 'coruna']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.chain', 0);
|
|
|
|
$this->getJson(route('admin.visits.data', ['range' => 'today', 'chain' => 'darksword']))
|
|
->assertOk()
|
|
->assertJsonPath('count', 1)
|
|
->assertJsonPath('data.0.chain', 1);
|
|
}
|
|
|
|
#[Test]
|
|
public function visits_logs_returns_ds_stages(): void
|
|
{
|
|
$uid = '50624FE26CC4A0DF689EAEA117557C3E';
|
|
PageVisit::query()->create([
|
|
'channel_id' => self::CHANNEL,
|
|
'client_uid' => $uid,
|
|
'chain' => PageVisit::CHAIN_DARKSWORD,
|
|
'os' => 'iOS',
|
|
'created_at' => now(),
|
|
]);
|
|
DsChainLog::record($uid, 'boot', 8, 'frame_boot', self::CHANNEL);
|
|
DsChainLog::record($uid, 'pe', 86, 'pe', self::CHANNEL);
|
|
|
|
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
|
|
$this->actingAs($admin, 'admin')
|
|
->getJson(route('admin.visits.logs', ['range' => 'today', 'client_uid' => $uid]))
|
|
->assertOk()
|
|
->assertJsonPath('data.0.stage', 'boot')
|
|
->assertJsonPath('data.0.stage_label', '启动')
|
|
->assertJsonPath('data.1.stage', 'pe')
|
|
->assertJsonPath('data.1.stage_label', '权限提升');
|
|
}
|
|
}
|