seed(RolePermissionSeeder::class); } public function test_agent_cannot_access_core_records_owned_by_another_agent(): void { $agent = $this->user('agent'); $otherAgent = $this->user('agent'); $lead = $this->lead($otherAgent); $company = Company::create(['name' => 'Private Company', 'owner_id' => $otherAgent->id, 'created_by' => $otherAgent->id]); $deal = Deal::create(['title' => 'Private Deal', 'owner_id' => $otherAgent->id, 'lead_id' => $lead->id, 'created_by' => $otherAgent->id]); $contact = Contact::create(['name' => 'Private Contact', 'lead_id' => $lead->id, 'company_id' => $company->id, 'created_by' => $otherAgent->id]); ContactPhone::create(['contact_id' => $contact->id, 'phone' => '09120000002', 'type' => 'mobile', 'status' => 'active']); $attachment = Attachment::create([ 'attachable_type' => Company::class, 'attachable_id' => $company->id, 'uploaded_by' => $otherAgent->id, 'original_name' => 'private.pdf', 'path' => 'attachments/private.pdf', 'mime_type' => 'application/pdf', 'size' => 10, ]); $this->actingAs($agent)->getJson("/api/companies/{$company->id}")->assertForbidden(); $this->actingAs($agent)->getJson("/api/deals/{$deal->id}")->assertForbidden(); $this->actingAs($agent)->putJson("/api/contacts/{$contact->id}", ['name' => 'Tampered'])->assertForbidden(); $this->actingAs($agent)->postJson('/api/notes', [ 'entity_type' => 'lead', 'entity_id' => $lead->id, 'content' => 'Unauthorized note', ])->assertForbidden(); $this->actingAs($agent)->getJson("/api/timeline?entity_type=lead&entity_id={$lead->id}")->assertForbidden(); $this->actingAs($agent)->getJson("/api/attachments?entity_type=company&entity_id={$company->id}")->assertForbidden(); $this->actingAs($agent)->getJson("/api/attachments/{$attachment->id}/download")->assertForbidden(); $contacts = $this->actingAs($agent)->getJson('/api/contacts')->assertOk(); $this->assertNotContains($contact->id, collect($contacts->json('data'))->pluck('id')->all()); } public function test_duplicate_check_does_not_disclose_out_of_scope_lead(): void { $agent = $this->user('agent'); $otherAgent = $this->user('agent'); $lead = $this->lead($otherAgent, ['phone' => '09125556677', 'company' => 'Hidden Corp']); $response = $this->actingAs($agent)->postJson('/api/duplicates/check', [ 'entity_type' => 'lead', 'phone' => '09125556677', ])->assertOk(); $this->assertNotContains($lead->id, collect($response->json('data'))->pluck('id')->all()); } public function test_supervisor_can_access_team_records_but_not_other_teams(): void { $supervisor = $this->user('supervisor'); $teamAgent = $this->user('agent'); $otherAgent = $this->user('agent'); $team = Team::create(['name' => 'Own Team', 'supervisor_id' => $supervisor->id, 'is_active' => true]); $otherTeam = Team::create(['name' => 'Other Team', 'is_active' => true]); $supervisor->teams()->attach($team); $teamAgent->teams()->attach($team); $otherAgent->teams()->attach($otherTeam); $teamLead = $this->lead($teamAgent, ['team_id' => $team->id]); $otherLead = $this->lead($otherAgent, ['team_id' => $otherTeam->id]); $this->actingAs($supervisor)->getJson("/api/leads/{$teamLead->id}")->assertOk(); $this->actingAs($supervisor)->getJson("/api/leads/{$otherLead->id}")->assertForbidden(); $this->actingAs($supervisor)->postJson('/api/notes', [ 'entity_type' => 'lead', 'entity_id' => $otherLead->id, 'content' => 'Unauthorized supervisor note', ])->assertForbidden(); } private function user(string $role): User { $user = User::factory()->create(['is_active' => true]); $user->assignRole($role); return $user; } private function lead(User $agent, array $attributes = []): Lead { return Lead::create(array_merge([ 'company' => 'Scoped Company', 'first_name' => 'Ali', 'last_name' => 'Karimi', 'phone' => fake()->unique()->numerify('021########'), 'assigned_to' => $agent->id, 'is_unassigned' => false, ], $attributes)); } }