artisan('lottery:admin-auth-sync')->assertExitCode(0); }); function grantAgentRoleManager(AdminUser $admin, AgentNode $agent): void { $now = now(); $roleId = DB::table('admin_roles')->insertGetId([ 'slug' => 'agent_mgr_'.$admin->id, 'code' => 'agent_mgr_'.$admin->id, 'name' => 'Agent Manager', 'scope_type' => 'system', 'status' => 1, 'is_system' => false, 'sort_order' => 0, 'created_at' => $now, 'updated_at' => $now, ]); $codes = [ 'agent.node.view', 'agent.node.manage', 'agent.role.view', 'agent.role.manage', 'agent.user.view', 'agent.user.manage', 'service.players.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, ]); } $siteId = (int) $agent->admin_site_id; 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' => $agent->id, 'is_primary' => true, 'granted_at' => $now, ]); DB::table('admin_user_agent_roles')->insert([ 'admin_user_id' => $admin->id, 'agent_node_id' => $agent->id, 'role_id' => $roleId, 'granted_at' => $now, ]); } test('agent operator can create role with subset of own permissions', function (): void { $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'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_role', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'role-branch', 'name' => 'Role Branch', ]); $operator = AdminUser::query()->create([ 'username' => 'role_ops', 'name' => 'Ops', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantAgentRoleManager($operator, $branch); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [ 'slug' => 'branch_cs', 'name' => 'Branch CS', 'permission_slugs' => ['prd.agent.view', 'prd.agent.role.view'], ]) ->assertOk() ->assertJsonPath('data.scope_type', 'agent') ->assertJsonPath('data.owner_agent_id', $branch->id); $this->withHeader('Authorization', 'Bearer '.$token) ->postJson('/api/v1/admin/agent-nodes/'.$branch->id.'/roles', [ 'slug' => 'branch_hack', 'name' => 'Hack', 'permission_slugs' => ['prd.dashboard.view'], ]) ->assertStatus(422); }); test('agent scoped roles are excluded from global admin roles index', function (): void { $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'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_idx', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'idx-branch', 'name' => 'Idx Branch', ]); DB::table('admin_roles')->insert([ 'slug' => 'idx_agent_role', 'code' => 'idx_agent_role', 'name' => 'Idx Agent Role', 'scope_type' => 'agent', 'owner_agent_id' => $branch->id, 'status' => 1, 'is_system' => false, 'sort_order' => 0, 'created_at' => now(), 'updated_at' => now(), ]); $slugs = collect($this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/admin-roles') ->json('data.items')) ->pluck('slug'); expect($slugs)->not->toContain('idx_agent_role'); }); test('players list respects agent subtree when agent_node_id is set', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $siteCode = (string) DB::table('admin_sites')->where('id', $siteId)->value('code'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_player', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branchA = $service->createChild($super, ['parent_id' => $rootId, 'code' => 'pa', 'name' => 'A']); $branchB = $service->createChild($super, ['parent_id' => $rootId, 'code' => 'pb', 'name' => 'B']); Player::query()->create([ 'site_code' => $siteCode, 'agent_node_id' => $branchA->id, 'site_player_id' => 'p-a', 'username' => 'pa', 'nickname' => 'PA', 'default_currency' => 'NPR', 'status' => 0, ]); Player::query()->create([ 'site_code' => $siteCode, 'agent_node_id' => $branchB->id, 'site_player_id' => 'p-b', 'username' => 'pb', 'nickname' => 'PB', 'default_currency' => 'NPR', 'status' => 0, ]); $operator = AdminUser::query()->create([ 'username' => 'player_ops', 'name' => 'Ops', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantAgentRoleManager($operator, $branchA); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $response = $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/players?site_code='.$siteCode); $response->assertOk(); $usernames = collect($response->json('data.items'))->pluck('username'); expect($usernames)->toContain('pa')->not->toContain('pb'); });