withoutMiddleware(EnsurePlatformSession::class); $this->user = User::create([ 'public_id' => 'assess-capture-user', 'name' => 'Capture User', 'email' => 'capture@example.com', ]); $this->organization = Organization::create([ 'owner_ref' => $this->user->public_id, 'name' => 'Capture Clinic', 'slug' => 'capture-clinic', 'timezone' => 'UTC', 'settings' => [ 'onboarded' => true, 'rollout' => [ CareFeatures::ASSESSMENTS_ENGINE => true, ], ], ]); $this->member = Member::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'user_ref' => $this->user->public_id, 'role' => 'doctor', ]); $this->branch = Branch::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'name' => 'Main', 'is_active' => true, ]); $this->patient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_number' => 'LC-CAP-001', 'first_name' => 'Ama', 'last_name' => 'Mensah', ]); $this->universal = AssessmentTemplate::create([ 'code' => 'universal_intake', 'name' => 'Universal Intake', 'category' => AssessmentTemplate::CATEGORY_UNIVERSAL, 'version' => 1, 'is_current' => true, 'is_active' => true, 'meta' => ['capture_roles' => ['doctor', 'nurse']], ]); AssessmentQuestion::create([ 'template_id' => $this->universal->id, 'code' => 'chief_complaint', 'label' => 'Chief complaint', 'answer_type' => AssessmentQuestion::TYPE_TEXT, 'is_required' => true, 'sort_order' => 1, ]); AssessmentQuestion::create([ 'template_id' => $this->universal->id, 'code' => 'pain_score', 'label' => 'Pain score', 'answer_type' => AssessmentQuestion::TYPE_SCALE, 'options' => ['min' => 0, 'max' => 10], 'is_required' => false, 'sort_order' => 2, ]); $this->nihss = AssessmentTemplate::create([ 'code' => 'nihss', 'name' => 'NIH Stroke Scale', 'category' => AssessmentTemplate::CATEGORY_DISEASE, 'version' => 1, 'is_current' => true, 'is_active' => true, 'scoring_strategy' => 'sum_items', 'meta' => ['capture_roles' => ['doctor']], ]); AssessmentQuestion::create([ 'template_id' => $this->nihss->id, 'code' => 'loc', 'label' => 'Level of consciousness', 'answer_type' => AssessmentQuestion::TYPE_SCORE_ITEM, 'options' => [ 'choices' => [ ['code' => '0', 'label' => 'Alert', 'score' => 0], ['code' => '1', 'label' => 'Not alert', 'score' => 1], ['code' => '2', 'label' => 'Obtunded', 'score' => 2], ], ], 'is_required' => true, 'sort_order' => 1, ]); } protected function setMemberRole(string $role): void { $this->member->update(['role' => $role]); } public function test_routes_404_when_feature_flag_disabled(): void { $this->organization->update([ 'settings' => ['onboarded' => true, 'rollout' => [CareFeatures::ASSESSMENTS_ENGINE => false]], ]); $this->actingAs($this->user) ->get(route('care.assessments.index', $this->patient)) ->assertNotFound(); } public function test_doctor_can_start_nihss(): void { $this->setMemberRole('doctor'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'nihss', ]) ->assertRedirect(); $assessment = Assessment::first(); $this->assertNotNull($assessment); $this->assertSame(Assessment::STATUS_DRAFT, $assessment->status); $this->assertSame($this->nihss->id, $assessment->template_id); $this->assertDatabaseHas('care_audit_logs', ['action' => 'assessment.started']); } public function test_nurse_can_start_universal_but_not_nihss(): void { $this->setMemberRole('nurse'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'universal_intake', ]) ->assertRedirect(); $this->assertSame(1, Assessment::count()); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'nihss', ]) ->assertForbidden(); } public function test_hospital_admin_can_start_nihss(): void { $this->setMemberRole('hospital_admin'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'nihss', ]) ->assertRedirect(); $this->assertSame(1, Assessment::where('template_id', $this->nihss->id)->count()); } public function test_super_admin_can_start_nihss(): void { $this->setMemberRole('super_admin'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'nihss', ]) ->assertRedirect(); $this->assertSame(1, Assessment::count()); } public function test_start_is_idempotent_for_same_draft(): void { $this->setMemberRole('doctor'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'nihss']) ->assertRedirect(); $first = Assessment::first(); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'nihss']) ->assertRedirect(route('care.assessments.show', $first)); $this->assertSame(1, Assessment::count()); } public function test_save_answers_complete_and_immutable(): void { $this->setMemberRole('doctor'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'universal_intake']) ->assertRedirect(); $assessment = Assessment::first(); $this->actingAs($this->user) ->put(route('care.assessments.update', $assessment), [ 'answers' => [ 'chief_complaint' => 'Headache', 'pain_score' => 4, ], 'notes' => 'Initial', ]) ->assertRedirect(route('care.assessments.show', $assessment)); $this->assertDatabaseHas('care_assessment_answers', [ 'assessment_id' => $assessment->id, 'value_text' => 'Headache', ]); $this->assertDatabaseHas('care_assessment_answers', [ 'assessment_id' => $assessment->id, 'value_number' => 4, ]); $this->actingAs($this->user) ->post(route('care.assessments.complete', $assessment)) ->assertRedirect(); $assessment->refresh(); $this->assertSame(Assessment::STATUS_COMPLETED, $assessment->status); $this->assertNotNull($assessment->completed_at); $this->assertDatabaseHas('care_audit_logs', ['action' => 'assessment.completed']); $this->actingAs($this->user) ->put(route('care.assessments.update', $assessment), [ 'answers' => ['chief_complaint' => 'Changed'], ]) ->assertSessionHasErrors('status'); $this->actingAs($this->user) ->post(route('care.assessments.complete', $assessment)) ->assertSessionHasErrors('status'); } public function test_complete_requires_required_answers(): void { $this->setMemberRole('nurse'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'universal_intake']) ->assertRedirect(); $assessment = Assessment::first(); $this->actingAs($this->user) ->post(route('care.assessments.complete', $assessment)) ->assertSessionHasErrors('answers.chief_complaint'); $this->assertSame(Assessment::STATUS_DRAFT, $assessment->fresh()->status); } public function test_cancel_draft_only(): void { $this->setMemberRole('doctor'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'universal_intake']) ->assertRedirect(); $assessment = Assessment::first(); $this->actingAs($this->user) ->post(route('care.assessments.cancel', $assessment)) ->assertRedirect(route('care.assessments.index', $this->patient)); $this->assertSame(Assessment::STATUS_CANCELLED, $assessment->fresh()->status); $this->assertDatabaseHas('care_audit_logs', ['action' => 'assessment.cancelled']); } public function test_consultation_scoped_start_links_consultation_and_visit(): void { $this->setMemberRole('doctor'); $visit = Visit::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $this->patient->id, 'status' => Visit::STATUS_OPEN, 'checked_in_at' => now(), ]); $consultation = Consultation::create([ 'owner_ref' => $this->user->public_id, 'visit_id' => $visit->id, 'patient_id' => $this->patient->id, 'status' => Consultation::STATUS_DRAFT, 'started_at' => now(), ]); $this->actingAs($this->user) ->post(route('care.consultations.assessments.store', $consultation), [ 'template_code' => 'universal_intake', ]) ->assertRedirect(); $assessment = Assessment::first(); $this->assertSame($consultation->id, $assessment->consultation_id); $this->assertSame($visit->id, $assessment->visit_id); $this->assertSame($this->branch->id, $assessment->branch_id); } public function test_public_bodies_use_consultation_uuid(): void { $this->setMemberRole('doctor'); $visit = Visit::create([ 'owner_ref' => $this->user->public_id, 'organization_id' => $this->organization->id, 'branch_id' => $this->branch->id, 'patient_id' => $this->patient->id, 'status' => Visit::STATUS_OPEN, 'checked_in_at' => now(), ]); $consultation = Consultation::create([ 'owner_ref' => $this->user->public_id, 'visit_id' => $visit->id, 'patient_id' => $this->patient->id, 'status' => Consultation::STATUS_DRAFT, 'started_at' => now(), ]); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), [ 'template_code' => 'universal_intake', 'consultation_uuid' => $consultation->uuid, ]) ->assertRedirect(); $assessment = Assessment::first(); $this->assertSame($consultation->id, $assessment->consultation_id); } public function test_tenant_isolation_returns_404(): void { $this->setMemberRole('doctor'); $otherUser = User::create([ 'public_id' => 'other-owner', 'name' => 'Other', 'email' => 'other@example.com', ]); $otherOrg = Organization::create([ 'owner_ref' => $otherUser->public_id, 'name' => 'Other Clinic', 'slug' => 'other-clinic', 'timezone' => 'UTC', 'settings' => ['onboarded' => true, 'rollout' => [CareFeatures::ASSESSMENTS_ENGINE => true]], ]); Member::create([ 'owner_ref' => $otherUser->public_id, 'organization_id' => $otherOrg->id, 'user_ref' => $otherUser->public_id, 'role' => 'doctor', ]); $otherPatient = Patient::create([ 'uuid' => (string) Str::uuid(), 'owner_ref' => $otherUser->public_id, 'organization_id' => $otherOrg->id, 'patient_number' => 'LC-OTHER', 'first_name' => 'Other', 'last_name' => 'Patient', ]); $this->actingAs($this->user) ->get(route('care.assessments.index', $otherPatient)) ->assertNotFound(); } public function test_score_item_stores_numeric_score_from_choice_code(): void { $this->setMemberRole('doctor'); $this->actingAs($this->user) ->post(route('care.assessments.store', $this->patient), ['template_code' => 'nihss']) ->assertRedirect(); $assessment = Assessment::first(); $this->actingAs($this->user) ->put(route('care.assessments.update', $assessment), [ 'answers' => ['loc' => '2'], ]) ->assertRedirect(); $this->assertDatabaseHas('care_assessment_answers', [ 'assessment_id' => $assessment->id, 'value_number' => 2, 'value_text' => null, ]); } public function test_receptionist_cannot_view_assessments(): void { $this->setMemberRole('receptionist'); $this->actingAs($this->user) ->get(route('care.assessments.index', $this->patient)) ->assertForbidden(); } }