getJson('/api/leads')->assertUnauthorized(); $this->postJson('/api/auth/logout')->assertUnauthorized(); } public function test_agent_cannot_modify_another_agents_lead(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $this->actingAs($agent) ->putJson("/api/leads/{$lead->id}", ['company' => 'Tampered Co']) ->assertForbidden(); $this->assertDatabaseMissing('leads', [ 'id' => $lead->id, 'company' => 'Tampered Co', ]); } public function test_agent_cannot_delete_another_agents_lead(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $this->actingAs($agent) ->deleteJson("/api/leads/{$lead->id}") ->assertForbidden(); $this->assertDatabaseHas('leads', ['id' => $lead->id, 'deleted_at' => null]); } public function test_agent_cannot_bulk_delete_another_agents_leads(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $this->actingAs($agent) ->deleteJson('/api/leads/bulk-delete', ['ids' => [$lead->id]]) ->assertForbidden(); $this->assertDatabaseHas('leads', ['id' => $lead->id, 'deleted_at' => null]); } public function test_agent_cannot_reassign_leads(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $this->actingAs($agent) ->postJson('/api/assignments/reassign', [ 'lead_id' => $lead->id, 'agent_id' => $agent->id, ]) ->assertForbidden(); $this->assertDatabaseHas('leads', [ 'id' => $lead->id, 'assigned_to' => $otherAgent->id, ]); } public function test_agent_cannot_modify_another_users_follow_up(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $followUp = FollowUp::create([ 'lead_id' => $lead->id, 'user_id' => $otherAgent->id, 'scheduled_at' => now()->addDay(), 'status' => 'pending', ]); $this->actingAs($agent) ->patchJson("/api/follow-ups/{$followUp->id}/mark-done") ->assertForbidden(); $this->assertDatabaseHas('follow_ups', [ 'id' => $followUp->id, 'status' => 'pending', ]); } public function test_agent_cannot_view_or_register_result_for_another_agents_call(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $call = Call::create([ 'lead_id' => $lead->id, 'user_id' => $otherAgent->id, 'direction' => 'outbound', 'phone' => '09129998877', ]); $this->actingAs($agent) ->getJson("/api/calls/{$call->id}") ->assertForbidden(); $this->actingAs($agent) ->postJson('/api/calls/register-result', [ 'call_id' => $call->id, 'result' => 'پاسخ داد', ]) ->assertForbidden(); $this->assertDatabaseHas('calls', [ 'id' => $call->id, 'result' => null, ]); } public function test_agent_cannot_create_follow_up_for_unauthorized_lead(): void { [$agent, $otherAgent] = $this->makeAgents(); $lead = $this->makeLead(['assigned_to' => $otherAgent->id, 'is_unassigned' => false]); $this->actingAs($agent) ->postJson('/api/follow-ups', [ 'lead_id' => $lead->id, 'scheduled_at' => now()->addDay()->toISOString(), 'notes' => 'Unauthorized follow-up', ]) ->assertForbidden(); $this->assertDatabaseMissing('follow_ups', [ 'lead_id' => $lead->id, 'user_id' => $agent->id, ]); } public function test_phone_numbers_and_recordings_are_masked_without_permissions(): void { [$agent] = $this->makeAgents(); Setting::create(['key' => 'phone_mask_enabled', 'value' => 'true', 'group' => 'security', 'type' => 'boolean']); $lead = $this->makeLead([ 'assigned_to' => $agent->id, 'is_unassigned' => false, 'phone' => '09121234567', 'phone_secondary' => '02199887766', ]); $call = Call::create([ 'lead_id' => $lead->id, 'user_id' => $agent->id, 'direction' => 'outbound', 'phone' => '09121234567', 'recording_url' => 'https://recordings.example.test/call.mp3', ]); $response = $this->actingAs($agent) ->getJson("/api/calls/{$call->id}") ->assertOk(); $this->assertStringNotContainsString('09121234567', $response->getContent()); $response->assertJsonPath('data.recording_url', null); } public function test_supervisor_cannot_access_other_team_lead_or_report(): void { $supervisorRole = Role::create(['name' => 'supervisor', 'guard_name' => 'web']); $agentRole = Role::create(['name' => 'agent', 'guard_name' => 'web']); $supervisor = User::factory()->create(['is_active' => true]); $ownAgent = User::factory()->create(['is_active' => true]); $otherAgent = User::factory()->create(['is_active' => true]); $supervisor->assignRole($supervisorRole); Permission::create(['name' => 'view_reports', 'guard_name' => 'web']); $supervisor->givePermissionTo('view_reports'); $ownAgent->assignRole($agentRole); $otherAgent->assignRole($agentRole); $ownTeam = Team::create(['name' => 'Own Team', 'supervisor_id' => $supervisor->id, 'is_active' => true]); $otherTeam = Team::create(['name' => 'Other Team', 'is_active' => true]); $supervisor->teams()->attach($ownTeam->id); $ownAgent->teams()->attach($ownTeam->id); $otherAgent->teams()->attach($otherTeam->id); $otherLead = $this->makeLead([ 'assigned_to' => $otherAgent->id, 'team_id' => $otherTeam->id, 'is_unassigned' => false, ]); $this->actingAs($supervisor) ->getJson("/api/leads/{$otherLead->id}") ->assertForbidden(); $this->actingAs($supervisor) ->getJson("/api/reports/agent-performance?agent_id={$otherAgent->id}") ->assertForbidden(); } /** * @return array{0: User, 1: User} */ private function makeAgents(): array { $agentRole = Role::create(['name' => 'agent', 'guard_name' => 'web']); $agent = User::factory()->create(['is_active' => true]); $otherAgent = User::factory()->create(['is_active' => true]); $agent->assignRole($agentRole); $otherAgent->assignRole($agentRole); return [$agent, $otherAgent]; } private function makeLead(array $attributes = []): Lead { return Lead::create(array_merge([ 'company' => 'Acme', 'first_name' => 'Ali', 'last_name' => 'Karimi', 'phone' => fake()->unique()->numerify('021########'), ], $attributes)); } }