artisan('lottery:admin-auth-sync')->assertExitCode(0); }); test('super admin can update agent profile with capability flags', 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' => 'profile_super', 'name' => 'Profile Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $child = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'profile-child', 'name' => 'Profile Child', 'username' => 'profile_child', 'total_share_rate' => 10, 'credit_limit' => 1000, ])); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id.'/profile', [ 'total_share_rate' => 12, 'credit_limit' => 1200, 'rebate_limit' => 0.01, 'default_player_rebate' => 0.005, 'settlement_cycle' => 'weekly', 'can_grant_extra_rebate' => false, 'can_create_child_agent' => true, 'can_create_player' => true, ]) ->assertOk() ->assertJsonPath('data.total_share_rate', 12) ->assertJsonPath('data.can_create_child_agent', true); }); test('super admin can update agent login username and tree reflects it', 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' => 'username_super', 'name' => 'Username Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $child = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'username-child', 'name' => 'Username Child', 'username' => 'old_login', ])); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id, [ 'username' => 'new_login', ]) ->assertOk() ->assertJsonPath('data.username', 'new_login'); $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/agent-nodes/tree?admin_site_id='.$siteId) ->assertOk() ->assertJsonPath('data.tree.0.children.0.username', 'new_login'); }); test('update can provision primary login when agent node had no bound account', 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' => 'provision_super', 'name' => 'Provision Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $child = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'provision-child', 'name' => 'Provision Child', 'username' => 'old_bound_login', ])); $userId = (int) DB::table('admin_user_agents')->where('agent_node_id', $child->id)->value('admin_user_id'); DB::table('admin_user_agent_roles')->where('agent_node_id', $child->id)->delete(); DB::table('admin_user_agents')->where('agent_node_id', $child->id)->delete(); AdminUser::query()->where('id', $userId)->delete(); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id, [ 'username' => 'fresh_login', 'password' => agentNodeTestPassword(), ]) ->assertOk() ->assertJsonPath('data.username', 'fresh_login'); expect( DB::table('admin_user_agents')->where('agent_node_id', $child->id)->where('is_primary', true)->count() )->toBe(1); }); test('agent profile update normalizes empty settlement cycle', 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' => 'profile_super3', 'name' => 'Profile Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $child = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'profile-child3', 'name' => 'Profile Child 3', 'username' => 'profile_child3', ])); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id.'/profile', [ 'settlement_cycle' => '', ]) ->assertOk() ->assertJsonPath('data.settlement_cycle', 'weekly'); }); test('bound agent cannot update own profile share and credit', 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' => 'self_profile_super', 'name' => 'Self Profile Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $agentNode = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'self-profile-agent', 'name' => 'Self Profile Agent', 'username' => 'self_profile_agent', 'total_share_rate' => 20, 'credit_limit' => 4000, ])); $agentUser = AdminUser::query()->where('username', 'self_profile_agent')->firstOrFail(); bindAdminUserToAgent($agentUser, $agentNode->id); $agentUser->syncPrimaryPlatformAgentRole($agentNode->id); $token = $agentUser->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$agentNode->id.'/profile', [ 'total_share_rate' => 99, 'credit_limit' => 999_999, ]) ->assertForbidden(); }); test('agent profile update rejects default rebate above limit', 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' => 'profile_super2', 'name' => 'Profile Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $child = $service->createChild($super, agentChildPayload([ 'parent_id' => $rootId, 'code' => 'profile-child2', 'name' => 'Profile Child 2', 'username' => 'profile_child2', ])); $token = $super->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->putJson('/api/v1/admin/agent-nodes/'.$child->id.'/profile', [ 'rebate_limit' => 0.005, 'default_player_rebate' => 0.01, ]) ->assertStatus(422) ->assertJsonPath('code', ErrorCode::ValidationFailed->value); });