create(['role' => UserRole::Manager]); $learner = User::factory()->create(['organization_id' => $manager->organization_id, 'role' => UserRole::Learner, 'name' => 'عضو تیم من']); $otherManager = User::factory()->create(['organization_id' => $manager->organization_id, 'role' => UserRole::Manager]); $outsider = User::factory()->create(['organization_id' => $manager->organization_id, 'role' => UserRole::Learner, 'name' => 'عضو تیم دیگر']); $team = Team::query()->create(['organization_id' => $manager->organization_id, 'name' => 'عملیات', 'status' => 'active', 'created_by' => $manager->getKey()]); $team->managers()->attach($manager); $team->members()->attach($learner); $otherTeam = Team::query()->create(['organization_id' => $manager->organization_id, 'name' => 'فروش', 'status' => 'active', 'created_by' => $otherManager->getKey()]); $otherTeam->managers()->attach($otherManager); $otherTeam->members()->attach($outsider); [$version, $assessment] = $this->content($manager); $assignment = Assignment::query()->create(['organization_id' => $manager->organization_id, 'assignable_type' => 'course', 'assignable_id' => $version->getKey(), 'target_type' => 'team', 'target_id' => $team->getKey(), 'status' => 'active', 'mandatory' => true, 'assigned_by' => $manager->getKey()]); $assignment->users()->attach($learner, ['status' => 'in_progress', 'assigned_at' => now(), 'due_at' => now()->addDays(2), 'progress' => 25, 'created_at' => now(), 'updated_at' => now()]); $otherAssignment = Assignment::query()->create(['organization_id' => $manager->organization_id, 'assignable_type' => 'course', 'assignable_id' => $version->getKey(), 'target_type' => 'team', 'target_id' => $otherTeam->getKey(), 'status' => 'active', 'mandatory' => true, 'assigned_by' => $otherManager->getKey()]); $otherAssignment->users()->attach($outsider, ['status' => 'completed', 'assigned_at' => now(), 'completed_at' => now(), 'progress' => 100, 'created_at' => now(), 'updated_at' => now()]); LearningEvent::query()->create(['organization_id' => $manager->organization_id, 'learner_id' => $learner->getKey(), 'client_event_id' => Str::uuid(), 'event_type' => 'lesson.started', 'assignment_id' => $assignment->getKey(), 'course_version_id' => $version->getKey(), 'payload' => [], 'occurred_at' => now(), 'received_at' => now()]); LearningEvent::query()->create(['organization_id' => $manager->organization_id, 'learner_id' => $outsider->getKey(), 'client_event_id' => Str::uuid(), 'event_type' => 'course.completed', 'assignment_id' => $otherAssignment->getKey(), 'course_version_id' => $version->getKey(), 'payload' => [], 'occurred_at' => now()->addSecond(), 'received_at' => now()]); AssessmentAttempt::query()->create(['organization_id' => $manager->organization_id, 'learner_id' => $learner->getKey(), 'course_version_id' => $version->getKey(), 'assessment_id' => $assessment->getKey(), 'attempt_number' => 1, 'status' => 'completed', 'score' => .8, 'started_at' => now()->subMinute(), 'completed_at' => now()]); AssessmentAttempt::query()->create(['organization_id' => $manager->organization_id, 'learner_id' => $outsider->getKey(), 'course_version_id' => $version->getKey(), 'assessment_id' => $assessment->getKey(), 'attempt_number' => 1, 'status' => 'completed', 'score' => .95, 'started_at' => now()->subMinute(), 'completed_at' => now()]); Sanctum::actingAs($manager); $this->getJson('/api/v1/manager/workspace')->assertOk() ->assertJsonPath('data.overview.teamCount', 1)->assertJsonPath('data.overview.memberCount', 1) ->assertJsonPath('data.overview.activeLearners', 1)->assertJsonPath('data.overview.dueSoon', 1) ->assertJsonPath('data.overview.engagement', 100)->assertJsonPath('data.overview.assessment', 80) ->assertJsonPath('data.overview.atRisk', 1)->assertJsonPath('data.members.0.name', 'عضو تیم من') ->assertJsonPath('data.recentActivity.0.learner', 'عضو تیم من') ->assertJsonCount(1, 'data.assessments') ->assertJsonPath('data.assessments.0.learnerId', $learner->getKey()) ->assertJsonMissing(['learner' => 'عضو تیم دیگر']) ->assertJsonMissing(['name' => 'عضو تیم دیگر']); } public function test_manager_endpoint_rejects_other_roles_and_manager_mutations_remain_forbidden(): void { $designer = User::factory()->create(['role' => UserRole::CourseDesigner]); Sanctum::actingAs($designer); $this->getJson('/api/v1/manager/workspace')->assertForbidden(); $manager = User::factory()->create(['organization_id' => $designer->organization_id, 'role' => UserRole::Manager]); Sanctum::actingAs($manager); $this->postJson('/api/v1/assignments', [])->assertForbidden(); $this->postJson('/api/v1/courses', [])->assertForbidden(); } public function test_manager_can_view_only_learning_assigned_to_their_own_account(): void { $manager = User::factory()->create(['role' => UserRole::Manager]); $teamMember = User::factory()->create(['organization_id' => $manager->organization_id, 'role' => UserRole::Learner]); [$version] = $this->content($manager); $managerAssignment = Assignment::query()->create([ 'organization_id' => $manager->organization_id, 'assignable_type' => 'course', 'assignable_id' => $version->getKey(), 'target_type' => 'individual', 'target_id' => $manager->getKey(), 'status' => 'active', 'mandatory' => true, 'assigned_by' => $manager->getKey(), ]); $managerAssignment->users()->attach($manager, [ 'status' => 'assigned', 'assigned_at' => now(), 'progress' => 0, 'created_at' => now(), 'updated_at' => now(), ]); $memberAssignment = Assignment::query()->create([ 'organization_id' => $manager->organization_id, 'assignable_type' => 'course', 'assignable_id' => $version->getKey(), 'target_type' => 'individual', 'target_id' => $teamMember->getKey(), 'status' => 'active', 'mandatory' => true, 'assigned_by' => $manager->getKey(), ]); $memberAssignment->users()->attach($teamMember, [ 'status' => 'assigned', 'assigned_at' => now(), 'progress' => 0, 'created_at' => now(), 'updated_at' => now(), ]); Sanctum::actingAs($manager); $this->getJson('/api/v1/auth/me') ->assertOk() ->assertJsonFragment(['learning.personal.view']); $this->getJson('/api/v1/learner/home') ->assertOk() ->assertJsonPath('data.summary.total', 1) ->assertJsonPath('data.assigned.0.id', $managerAssignment->getKey()) ->assertJsonMissing(['id' => $memberAssignment->getKey()]); $this->getJson('/api/v1/learner/assignments/'.$memberAssignment->getKey())->assertNotFound(); } private function content(User $owner): array { $course = Course::query()->create(['organization_id' => $owner->organization_id, 'title' => 'ایمنی', 'slug' => 'manager-safety', 'status' => 'published', 'created_by' => $owner->getKey()]); $version = CourseVersion::query()->create(['organization_id' => $owner->organization_id, 'course_id' => $course->getKey(), 'version_number' => 1, 'status' => CourseVersionStatus::Published, 'title' => 'ایمنی محیط کار', 'settings' => [], 'completion_rules' => [], 'published_at' => now()]); $assessment = Assessment::query()->create(['organization_id' => $owner->organization_id, 'course_version_id' => $version->getKey(), 'title' => 'ارزیابی ایمنی', 'settings' => []]); return [$version, $assessment]; } }