seed(RolePermissionSeeder::class); } public function test_quality_review_derives_agent_from_call_and_versions_reviews(): void { [$supervisor, $agent] = $this->team(); $otherAgent = $this->user('agent'); $call = $this->makeCall($agent); $first = $this->actingAs($supervisor)->postJson('/api/quality-reviews', $this->reviewPayload($call, [ 'agent_id' => $otherAgent->id, 'is_shared_with_agent' => false, ]))->assertCreated(); $this->assertSame($agent->id, $first->json('agent_id')); $this->assertSame(1, $first->json('version')); $second = $this->actingAs($supervisor)->postJson('/api/quality-reviews', $this->reviewPayload($call, [ 'is_shared_with_agent' => true, ]))->assertCreated(); $this->assertSame(2, $second->json('version')); $this->assertFalse(QualityReview::findOrFail($first->json('id'))->is_current); $this->assertTrue(QualityReview::findOrFail($second->json('id'))->is_current); } public function test_agent_only_sees_shared_own_review_and_cannot_create_one(): void { [$supervisor, $agent] = $this->team(); $call = $this->makeCall($agent); $review = $this->actingAs($supervisor)->postJson('/api/quality-reviews', $this->reviewPayload($call, [ 'is_shared_with_agent' => false, ]))->assertCreated()->json(); $this->actingAs($agent)->getJson("/api/quality-reviews/{$review['id']}")->assertForbidden(); $this->actingAs($agent)->postJson('/api/quality-reviews', $this->reviewPayload($call))->assertForbidden(); $this->actingAs($supervisor)->putJson("/api/quality-reviews/{$review['id']}", ['is_shared_with_agent' => true])->assertOk(); $this->actingAs($agent)->getJson("/api/quality-reviews/{$review['id']}")->assertOk(); } public function test_sales_scripts_are_transactional_and_agents_only_see_active_scripts(): void { [$supervisor, $agent] = $this->team(); $scriptId = $this->actingAs($supervisor)->postJson('/api/scripts', [ 'title' => 'Outbound sales', 'description' => 'Approved script', 'is_active' => true, 'assigned_user_ids' => [$agent->id], 'sections' => [ ['title' => 'Greeting', 'content' => 'Hello', 'sort_order' => 0], ['title' => 'Discovery', 'content' => 'Ask questions', 'sort_order' => 1], ], ])->assertCreated()->json('id'); $this->assertDatabaseHas('script_sections', ['sales_script_id' => $scriptId, 'sort_order' => 0]); $this->actingAs($agent)->getJson("/api/scripts/{$scriptId}")->assertOk(); $this->actingAs($agent)->postJson('/api/scripts', ['title' => 'Unauthorized'])->assertForbidden(); SalesScript::whereKey($scriptId)->update(['is_active' => false]); $this->actingAs($agent)->getJson("/api/scripts/{$scriptId}")->assertForbidden(); $this->actingAs($supervisor)->postJson('/api/scripts', [ 'title' => 'Invalid script', 'sections' => [['title' => 'Missing content']], ])->assertUnprocessable(); $this->assertDatabaseMissing('sales_scripts', ['title' => 'Invalid script']); } private function reviewPayload(Call $call, array $overrides = []): array { return array_merge([ 'call_id' => $call->id, 'greeting_score' => 80, 'product_intro_score' => 75, 'needs_discovery_score' => 70, 'objection_handling_score' => 65, 'closing_score' => 60, 'crm_accuracy_score' => 90, 'follow_up_quality_score' => 85, 'feedback' => 'Useful feedback', ], $overrides); } private function team(): array { $supervisor = $this->user('supervisor'); $agent = $this->user('agent'); $team = Team::create(['name' => 'Sales Team', 'supervisor_id' => $supervisor->id, 'is_active' => true]); $supervisor->teams()->attach($team); $agent->teams()->attach($team); return [$supervisor, $agent]; } private function makeCall(User $agent): Call { $lead = Lead::create([ 'company' => 'Call Company', 'first_name' => 'Sara', 'last_name' => 'Ahmadi', 'phone' => fake()->unique()->numerify('021########'), 'assigned_to' => $agent->id, 'is_unassigned' => false, ]); return Call::create(['lead_id' => $lead->id, 'user_id' => $agent->id, 'direction' => 'outbound', 'phone' => $lead->phone]); } private function user(string $role): User { $user = User::factory()->create(['is_active' => true]); $user->assignRole($role); return $user; } }