Files
ladill-care/tests/Feature/CareStrokeMvpTest.php
T
isaacclad 6c9051d2c6
Deploy Ladill Care / deploy (push) Successful in 1m22s
fix(ui): use clinician-friendly assessment copy
Replace internal design jargon (Layer 1, narrative source of truth,
pathway instruments) with plain clinical language on consultations,
patient charts, settings, and nav.
2026-07-16 23:17:08 +00:00

290 lines
10 KiB
PHP

<?php
namespace Tests\Feature;
use App\Http\Middleware\EnsurePlatformSession;
use App\Models\Assessment;
use App\Models\AssessmentScore;
use App\Models\AssessmentTemplate;
use App\Models\Branch;
use App\Models\ClinicalPathway;
use App\Models\Consultation;
use App\Models\Diagnosis;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\PatientPathway;
use App\Models\User;
use App\Models\Visit;
use App\Services\Care\CareFeatures;
use App\Services\Care\ScoringService;
use Database\Seeders\AssessmentTemplateSeeder;
use Database\Seeders\ClinicalPathwaySeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use Tests\TestCase;
/**
* PR 6 — Stroke MVP content packs (NIHSS + mRS) and M2 vertical slice.
*/
class CareStrokeMvpTest extends TestCase
{
use RefreshDatabase;
protected User $user;
protected Organization $organization;
protected Branch $branch;
protected Patient $patient;
protected Member $member;
protected function setUp(): void
{
parent::setUp();
$this->withoutMiddleware(EnsurePlatformSession::class);
$this->user = User::create([
'public_id' => 'stroke-mvp-user',
'name' => 'Stroke MVP',
'email' => 'stroke@example.com',
]);
$this->organization = Organization::create([
'owner_ref' => $this->user->public_id,
'name' => 'Stroke Clinic',
'slug' => 'stroke-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-STROKE-001',
'first_name' => 'Efua',
'last_name' => 'Asante',
]);
$this->seed(AssessmentTemplateSeeder::class);
$this->seed(ClinicalPathwaySeeder::class);
}
public function test_seed_packs_load_nihss_and_mrs(): void
{
$nihss = AssessmentTemplate::currentSystemByCode('nihss');
$mrs = AssessmentTemplate::currentSystemByCode('mrs');
$this->assertNotNull($nihss);
$this->assertNotNull($mrs);
$this->assertSame('sum_items', $nihss->scoring_strategy);
$this->assertSame('single_value', $mrs->scoring_strategy);
$this->assertSame(15, $nihss->questions()->count());
$this->assertSame(1, $mrs->questions()->count());
$this->assertSame(['doctor'], $nihss->meta['capture_roles'] ?? null);
$pathway = ClinicalPathway::findByCode('stroke');
$this->assertNotNull($pathway);
$codes = $pathway->templates()->pluck('template_code')->all();
$this->assertContains('nihss', $codes);
$this->assertContains('mrs', $codes);
$required = $pathway->templates()->where('is_required_on_activation', true)->pluck('template_code')->all();
$this->assertEqualsCanonicalizing(['nihss', 'mrs'], $required);
}
public function test_golden_mrs_score_three(): void
{
$this->actingAs($this->user)
->post(route('care.assessments.store', $this->patient), ['template_code' => 'mrs'])
->assertRedirect();
$assessment = Assessment::first();
$this->actingAs($this->user)
->put(route('care.assessments.update', $assessment), [
'answers' => ['mrs_score' => '3'],
])
->assertRedirect();
$this->actingAs($this->user)
->post(route('care.assessments.complete', $assessment))
->assertRedirect();
$score = AssessmentScore::where('assessment_id', $assessment->id)->first();
$this->assertNotNull($score);
$this->assertEquals(3, (float) $score->total_score);
$this->assertEquals(6, (float) $score->max_score);
$this->assertSame('mrs', $score->template_code);
}
public function test_golden_nihss_fixture_total_twelve_max_forty_two(): void
{
// Moderate left hemisphere pattern → total 12; max 42.
$answers = [
'1a_loc' => '1',
'1b_loc_questions' => '1',
'1c_loc_commands' => '0',
'2_gaze' => '0',
'3_visual' => '1',
'4_facial' => '2',
'5a_motor_arm_left' => '2',
'5b_motor_arm_right' => '0',
'6a_motor_leg_left' => '2',
'6b_motor_leg_right' => '0',
'7_ataxia' => '0',
'8_sensory' => '1',
'9_language' => '1',
'10_dysarthria' => '1',
'11_extinction' => '0',
];
// 1+1+0+0+1+2+2+0+2+0+0+1+1+1+0 = 12
$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' => $answers])
->assertRedirect();
$preview = app(ScoringService::class)->preview(
$assessment->fresh(['template.questions', 'answers.question'])
);
$this->assertEquals(12.0, $preview['total']);
$this->assertEquals(42.0, $preview['max']);
$this->actingAs($this->user)
->post(route('care.assessments.complete', $assessment))
->assertRedirect();
$score = AssessmentScore::first();
$this->assertEquals(12, (float) $score->total_score);
$this->assertEquals(42, (float) $score->max_score);
$this->assertArrayHasKey('loc', $score->subscores);
$this->assertEquals(2.0, (float) $score->subscores['loc']); // 1a+1b+1c = 2
}
public function test_m2_e2e_activate_stroke_complete_nihss_and_mrs(): void
{
$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(),
]);
Diagnosis::create([
'owner_ref' => $this->user->public_id,
'consultation_id' => $consultation->id,
'code' => 'I63.9',
'description' => 'Cerebral infarction, unspecified',
'is_primary' => true,
]);
$this->actingAs($this->user)
->get(route('care.consultations.show', $consultation))
->assertOk()
->assertSee('Stroke')
->assertSee('Activate');
$this->actingAs($this->user)
->post(route('care.pathways.store', $this->patient), [
'pathway_code' => 'stroke',
'consultation_uuid' => $consultation->uuid,
])
->assertRedirect();
$this->assertSame(1, PatientPathway::where('status', PatientPathway::STATUS_ACTIVE)->count());
$this->assertSame(2, Assessment::where('status', Assessment::STATUS_DRAFT)->count());
$this->actingAs($this->user)
->get(route('care.consultations.show', $consultation))
->assertOk()
->assertSee('Recommended forms')
->assertSee('NIH Stroke Scale')
->assertSee('Modified Rankin Scale')
->assertSee('Continue');
$nihss = Assessment::query()
->whereHas('template', fn ($q) => $q->where('code', 'nihss'))
->first();
$mrs = Assessment::query()
->whereHas('template', fn ($q) => $q->where('code', 'mrs'))
->first();
$this->assertNotNull($nihss);
$this->assertNotNull($mrs);
$this->assertSame($consultation->id, $nihss->consultation_id);
// Minimal valid NIHSS (all zeros) → total 0.
$zeroAnswers = collect($nihss->template->questions)->mapWithKeys(
fn ($q) => [$q->code => '0']
)->all();
$this->actingAs($this->user)
->put(route('care.assessments.update', $nihss), ['answers' => $zeroAnswers])
->assertRedirect();
$this->actingAs($this->user)
->post(route('care.assessments.complete', $nihss))
->assertRedirect();
$this->assertEquals(0, (float) AssessmentScore::where('assessment_id', $nihss->id)->value('total_score'));
$this->assertEquals(42, (float) AssessmentScore::where('assessment_id', $nihss->id)->value('max_score'));
$this->actingAs($this->user)
->put(route('care.assessments.update', $mrs), ['answers' => ['mrs_score' => '2']])
->assertRedirect();
$this->actingAs($this->user)
->post(route('care.assessments.complete', $mrs))
->assertRedirect();
$this->assertEquals(2, (float) AssessmentScore::where('assessment_id', $mrs->id)->value('total_score'));
$this->actingAs($this->user)
->get(route('care.consultations.show', $consultation))
->assertOk()
->assertSee('score 0')
->assertSee('score 2');
}
public function test_nurse_cannot_start_seeded_nihss(): void
{
$this->member->update(['role' => 'nurse']);
$this->actingAs($this->user)
->post(route('care.assessments.store', $this->patient), ['template_code' => 'nihss'])
->assertForbidden();
}
}