newInstanceWithoutConstructor(); $students = $this->privateMethodInvoker($service, 'promotionStudentsWithStatus')([ [ 'student_id' => 17, 'student_name' => 'Amina Rahman', 'school_id' => 'S-1042', 'class_section_name' => 'Grade 4 - A', 'status' => 'missing', ], [ 'student_id' => 18, 'student_name' => 'Omar Ali', 'school_id' => 'S-1043', 'class_section_name' => 'Grade 5 - B', 'status' => 'decided', ], ], 'missing'); $this->assertSame([[ 'student_id' => 17, 'student_name' => 'Amina Rahman', 'school_id' => 'S-1042', 'class_section_name' => 'Grade 4 - A', ]], $students); } public function testLegacyManualDecisionPreventsFalseMissingDecisionBlocker(): void { $service = (new ReflectionClass(SchoolYearClosingService::class))->newInstanceWithoutConstructor(); $merge = $this->privateMethodInvoker($service, 'mergeFallbackPromotionDecisions'); $decisions = $merge([], [[ 'student_id' => 191, 'decision' => 'Make-up exam in fall', 'notes' => 'Make-up exam approved.', ]]); $this->assertSame('decided', $decisions[191]['status']); $this->assertSame('MAKE_UP_EXAM', $decisions[191]['normalized_decision']); $this->assertSame('manual', $decisions[191]['source']); } public function testLegacyManualDecisionReplacesStalePendingConsolidatedDecision(): void { $service = (new ReflectionClass(SchoolYearClosingService::class))->newInstanceWithoutConstructor(); $merge = $this->privateMethodInvoker($service, 'mergeFallbackPromotionDecisions'); $decisions = $merge([ 191 => ['status' => 'pending', 'decision' => '', 'source' => 'pending'], ], [[ 'student_id' => 191, 'decision' => 'Make-up exam in fall', 'notes' => '', ]]); $this->assertSame('decided', $decisions[191]['status']); $this->assertSame('Make-up exam in fall', $decisions[191]['decision']); } private function privateMethodInvoker(object $object, string $method): callable { $reflection = new ReflectionClass($object); $reflectionMethod = $reflection->getMethod($method); return static fn (...$arguments) => $reflectionMethod->invoke($object, ...$arguments); } }