service(); $evaluation = [ 'deliberation_decision' => DeliberationDecision::PASSED, 'placement_status' => 'exit_required', 'source_grade_name' => '8', 'adult_student' => false, 'blockers' => [ 'The student has passed the highest available grade and must follow the school completion or exit process.', 'Registration for the new school year has not opened yet.', ], ]; $status = $this->invoke($service, 'registrationStatus', [$evaluation]); $message = $this->invoke($service, 'decisionMessage', ['Student Name', $evaluation, 'August 1, 2026', 'August 31, 2026']); $action = $this->invoke($service, 'requiredAction', [$evaluation, 'August 31, 2026']); $this->assertSame('Eligible', $status); $this->assertStringContainsString('We are pleased to inform you that Student Name has successfully passed Grade 8.', $message); $this->assertStringContainsString('Re-enrollment for the new school year will open on August 1, 2026.', $message); $this->assertStringContainsString('Please make sure to re-enroll your child before August 31, 2026', $message); $this->assertStringContainsString('sign in to the parent portal', $message); $this->assertStringContainsString('Complete re-enrollment before August 31, 2026.', $action); $this->assertStringNotContainsString('Not Eligible', $status); $this->assertStringNotContainsString('Registration for the new school year has not opened yet', $message); $this->assertStringNotContainsString('Contact the school administration.', $action); } public function testFinancialSectionIsHiddenWhenNothingIsDue(): void { $service = $this->service(); $html = $this->invoke($service, 'financialSection', [10, [ 'registration_fee' => 0, 'tuition_due_at_registration' => 0, 'mandatory_fees' => 0, ], null]); $this->assertSame('', $html); } public function testFinancialSectionShowsWhenAnyAmountIsDue(): void { $service = $this->service(); $html = $this->invoke($service, 'financialSection', [10, [ 'registration_fee' => 25, 'tuition_due_at_registration' => 0, 'mandatory_fees' => 0, ], null]); $this->assertStringContainsString('Family Account Information', $html); $this->assertStringContainsString('Total currently due: $25.00', $html); } public function testAutomaticDistributionPlacementShowsOnlyGrade(): void { $service = $this->service(); $placement = $this->invoke($service, 'placementText', [[ 'placement_status' => 'automatic_distribution_pending', 'assigned_grade_name' => '9', ]]); $this->assertSame('Grade 9', $placement); } public function testForceCannotSendWithoutAdminLaunchApproval(): void { $emailService = $this->createMock(EmailService::class); $emailService->expects($this->never())->method('send'); $service = $this->service($this->dbWithNoRecipientFamilies(), $emailService); $summary = $service->sendForSchoolYear([ 'name' => '2026-2027', 'registration_launch_approved_at' => null, ], null, false, true); $this->assertSame(0, $summary['sent']); $this->assertSame(1, $summary['skipped']); $this->assertStringContainsString('not approved', implode(' ', $summary['messages'])); } /** * @param list $args */ private function invoke(EnrollmentRegistrationEmailService $service, string $method, array $args): mixed { $reflection = new \ReflectionMethod($service, $method); $reflection->setAccessible(true); return $reflection->invokeArgs($service, $args); } private function service(?BaseConnection $db = null, ?EmailService $emailService = null): EnrollmentRegistrationEmailService { $db ??= $this->createMock(BaseConnection::class); return new EnrollmentRegistrationEmailService( $db, new EnrollmentTransitionService($db), $emailService ?? $this->createMock(EmailService::class), ); } private function dbWithNoRecipientFamilies(): BaseConnection { $query = new class { public function getResultArray(): array { return []; } }; $builder = $this->createMock(BaseBuilder::class); $builder->method('select')->willReturnSelf(); $builder->method('where')->willReturnSelf(); $builder->method('get')->willReturn($query); $db = $this->createMock(BaseConnection::class); $db->method('table')->willReturn($builder); $db->method('fieldExists')->with('user_type', 'users')->willReturn(false); return $db; } }