- 在多个控制器中引入 agent_node_id,以支持基于代理节点的权限和数据过滤。 - 更新 AdminRole 和 AdminUser 模型,新增角色范围和代理节点相关功能,提升角色管理的灵活性。 - 在请求验证中添加 agent_node_id 字段,确保 API 接口支持代理节点的相关操作。 - 优化 LotterySettings 服务,支持批量写入设置,提升配置管理的效率。 - 更新仪表板和报告服务,增强数据统计功能,确保管理员能够获取更全面的统计信息。
206 lines
6.9 KiB
PHP
206 lines
6.9 KiB
PHP
<?php
|
|
|
|
use App\Models\AdminUser;
|
|
use App\Lottery\ErrorCode;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
test('admin ping requires authentication', function () {
|
|
$this->getJson('/api/v1/admin/ping')->assertUnauthorized()
|
|
->assertJsonPath('code', ErrorCode::AdminUnauthenticated->value);
|
|
});
|
|
|
|
test('admin auth me returns current admin profile', function () {
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'admin_me',
|
|
'name' => '管理员本人',
|
|
'email' => null,
|
|
'password' => 'secret-strong',
|
|
'status' => 0,
|
|
]);
|
|
|
|
$roleId = DB::table('admin_roles')->insertGetId([
|
|
'code' => 'super_admin',
|
|
'slug' => 'super_admin',
|
|
'name' => '超级管理员',
|
|
'description' => null,
|
|
'status' => 1,
|
|
'is_system' => true,
|
|
'sort_order' => 0,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
$siteId = DB::table('admin_sites')->insertGetId([
|
|
'code' => 'default',
|
|
'name' => '默认站点',
|
|
'is_default' => true,
|
|
'status' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
DB::table('admin_user_site_roles')->insert([
|
|
'admin_user_id' => $admin->id,
|
|
'site_id' => $siteId,
|
|
'role_id' => $roleId,
|
|
'granted_at' => now(),
|
|
]);
|
|
|
|
$token = $admin->createToken('admin-api', ['*'], now()->addDay())->plainTextToken;
|
|
|
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
|
->getJson('/api/v1/admin/auth/me')
|
|
->assertOk()
|
|
->assertJsonPath('code', ErrorCode::Success->value)
|
|
->assertJsonPath('data.admin.username', 'admin_me')
|
|
->assertJsonPath('data.admin.navigation.0.segment', 'dashboard');
|
|
});
|
|
|
|
test('admin login returns bearer token when captcha passes validation', function () {
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'tester',
|
|
'name' => '测试昵称',
|
|
'email' => null,
|
|
'password' => 'secret-strong',
|
|
'status' => 0,
|
|
]);
|
|
// 登录返回的 navigation 需要管理员权限(避免测试依赖默认 DB 状态)
|
|
grantSuperAdminRole($admin);
|
|
|
|
$captchaKey = (string) Str::uuid();
|
|
Cache::put(
|
|
'admin_captcha:'.$captchaKey,
|
|
hash_hmac('sha256', 'xwz2', (string) config('app.key')),
|
|
now()->addSeconds(120),
|
|
);
|
|
|
|
$resp = $this->postJson('/api/v1/admin/auth/login', [
|
|
'account' => 'Tester',
|
|
'password' => 'secret-strong',
|
|
'captcha_key' => $captchaKey,
|
|
'captcha_code' => 'xwz2',
|
|
]);
|
|
|
|
$resp->assertOk()
|
|
->assertJsonPath('code', ErrorCode::Success->value)
|
|
->assertJsonPath('data.admin.username', 'tester')
|
|
->assertJsonPath('data.admin.nickname', '测试昵称')
|
|
->assertJsonPath('data.admin.navigation.0.segment', 'dashboard')
|
|
->assertJsonPath('data.admin.navigation.0.href', '/admin')
|
|
->assertJsonPath('data.admin.navigation.0.nav_group', 'overview')
|
|
->assertJsonPath('data.admin.navigation.1.segment', 'agents')
|
|
->assertJsonPath('data.admin.navigation.1.nav_group', 'agent')
|
|
->assertJsonPath('data.admin.navigation.2.segment', 'draws')
|
|
->assertJsonStructure(['data' => ['token', 'token_type', 'admin' => ['id', 'username', 'nickname', 'email', 'permissions', 'navigation']]]);
|
|
|
|
$token = $resp->json('data.token');
|
|
expect($token)->not->toBeNull();
|
|
|
|
$this->withHeader('Authorization', 'Bearer '.$token)
|
|
->getJson('/api/v1/admin/ping')
|
|
->assertOk()
|
|
->assertJsonPath('data.scope', 'admin');
|
|
});
|
|
|
|
test('agent operator auth me omits platform-only navigation', function (): void {
|
|
$this->artisan('lottery:admin-auth-sync')->assertExitCode(0);
|
|
|
|
$siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id');
|
|
$rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id');
|
|
|
|
$admin = AdminUser::query()->create([
|
|
'username' => 'agent_nav_ops',
|
|
'name' => 'Agent Nav',
|
|
'email' => null,
|
|
'password' => 'secret-strong',
|
|
'status' => 0,
|
|
]);
|
|
|
|
$now = now();
|
|
$roleId = DB::table('admin_roles')->insertGetId([
|
|
'slug' => 'agent_nav_ops_role',
|
|
'code' => 'agent_nav_ops_role',
|
|
'name' => 'Agent Nav Ops',
|
|
'description' => null,
|
|
'status' => 1,
|
|
'is_system' => false,
|
|
'sort_order' => 0,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$codes = ['agent.node.view', 'service.report.view'];
|
|
$actionIds = DB::table('admin_menu_actions')->whereIn('permission_code', $codes)->pluck('id');
|
|
foreach ($actionIds as $actionId) {
|
|
DB::table('admin_role_menu_actions')->insert([
|
|
'role_id' => $roleId,
|
|
'menu_action_id' => (int) $actionId,
|
|
]);
|
|
}
|
|
|
|
DB::table('admin_user_site_roles')->insert([
|
|
'admin_user_id' => $admin->id,
|
|
'site_id' => $siteId,
|
|
'role_id' => $roleId,
|
|
'granted_at' => $now,
|
|
]);
|
|
DB::table('admin_user_agents')->insert([
|
|
'admin_user_id' => $admin->id,
|
|
'agent_node_id' => $rootId,
|
|
'is_primary' => true,
|
|
'granted_at' => $now,
|
|
]);
|
|
|
|
$token = $admin->createToken('test', ['*'], now()->addDay())->plainTextToken;
|
|
|
|
$segments = $this->withHeader('Authorization', 'Bearer '.$token)
|
|
->getJson('/api/v1/admin/auth/me')
|
|
->assertOk()
|
|
->json('data.admin.navigation');
|
|
|
|
$keys = array_column($segments, 'segment');
|
|
expect($keys)->toContain('agents', 'reports')
|
|
->and($keys)->not->toContain('admin_users', 'admin_roles', 'settings', 'integration', 'rules_plays');
|
|
});
|
|
|
|
test('admin captcha exposes key and image base64', function () {
|
|
$resp = $this->getJson('/api/v1/admin/auth/captcha');
|
|
|
|
$resp->assertOk()
|
|
->assertJsonPath('code', ErrorCode::Success->value);
|
|
|
|
$data = $resp->json('data');
|
|
expect($data)->toBeArray()
|
|
->and(Str::isUuid((string) $data['captcha_key']))->toBeTrue()
|
|
->and((string) $data['image_base64'])->not->toBe('');
|
|
});
|
|
|
|
test('login rejects wrong password with masked message', function () {
|
|
AdminUser::query()->create([
|
|
'username' => 'bad_tester',
|
|
'name' => 'X',
|
|
'email' => null,
|
|
'password' => 'right-only',
|
|
'status' => 0,
|
|
]);
|
|
|
|
$captchaKey = (string) Str::uuid();
|
|
Cache::put(
|
|
'admin_captcha:'.$captchaKey,
|
|
hash_hmac('sha256', 'aaaa', (string) config('app.key')),
|
|
now()->addSeconds(120),
|
|
);
|
|
|
|
$this->postJson('/api/v1/admin/auth/login', [
|
|
'account' => 'bad_tester',
|
|
'password' => 'wrong-password',
|
|
'captcha_key' => $captchaKey,
|
|
'captcha_code' => 'aaaa',
|
|
])->assertUnauthorized()
|
|
->assertJsonPath('code', ErrorCode::AdminCredentialsInvalid->value);
|
|
});
|