tempDir = sys_get_temp_dir() . '/doctor_loader_test_' . uniqid(); mkdir($this->tempDir, 0755, true); // Reset DoctorLoader state $reflection = new \ReflectionClass(DoctorLoader::class); $property = $reflection->getProperty('doctor_items'); $property->setAccessible(true); $property->setValue(null, []); $property = $reflection->getProperty('fix_items'); $property->setAccessible(true); $property->setValue(null, []); } protected function tearDown(): void { parent::tearDown(); // Clean up temp directory if (is_dir($this->tempDir)) { $this->removeDirectory($this->tempDir); } // Reset DoctorLoader state $reflection = new \ReflectionClass(DoctorLoader::class); $property = $reflection->getProperty('doctor_items'); $property->setAccessible(true); $property->setValue(null, []); $property = $reflection->getProperty('fix_items'); $property->setAccessible(true); $property->setValue(null, []); } public function testGetDoctorItemsReturnsEmptyArrayInitially(): void { $this->assertEmpty(DoctorLoader::getDoctorItems()); } public function testLoadFromClassWithCheckItemAttribute(): void { $class = new class { #[CheckItem('test-check', level: 1)] public function testCheck(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(1, $items); $this->assertInstanceOf(CheckItem::class, $items[0][0]); $this->assertEquals('test-check', $items[0][0]->item_name); $this->assertEquals(1, $items[0][0]->level); } public function testLoadFromClassWithMultipleCheckItems(): void { $class = new class { #[CheckItem('check-1', level: 2)] public function check1(): void {} #[CheckItem('check-2', level: 1)] public function check2(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(2, $items); } public function testLoadFromClassSortsByLevelDescending(): void { $class = new class { #[CheckItem('low-priority', level: 1)] public function lowCheck(): void {} #[CheckItem('high-priority', level: 5)] public function highCheck(): void {} #[CheckItem('medium-priority', level: 3)] public function mediumCheck(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(3, $items); // Should be sorted by level descending: 5, 3, 1 $this->assertEquals(5, $items[0][0]->level); $this->assertEquals(3, $items[1][0]->level); $this->assertEquals(1, $items[2][0]->level); } public function testLoadFromClassWithoutSorting(): void { $class = new class { #[CheckItem('check-1', level: 1)] public function check1(): void {} #[CheckItem('check-2', level: 5)] public function check2(): void {} }; DoctorLoader::loadFromClass(get_class($class), false); $items = DoctorLoader::getDoctorItems(); // Without sorting, items should be in order they were added $this->assertCount(2, $items); } public function testLoadFromClassWithFixItemAttribute(): void { $class = new class { #[FixItem('test-fix')] public function testFix(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $fixItem = DoctorLoader::getFixItem('test-fix'); $this->assertNotNull($fixItem); $this->assertTrue(is_callable($fixItem)); } public function testLoadFromClassWithMultipleFixItems(): void { $class = new class { #[FixItem('fix-1')] public function fix1(): void {} #[FixItem('fix-2')] public function fix2(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $this->assertNotNull(DoctorLoader::getFixItem('fix-1')); $this->assertNotNull(DoctorLoader::getFixItem('fix-2')); } public function testGetFixItemReturnsNullForNonExistent(): void { $this->assertNull(DoctorLoader::getFixItem('non-existent-fix')); } public function testLoadFromClassWithOptionalCheckOnClass(): void { // Note: OptionalCheck expects an array, not a callable directly // This test verifies the structure even though we can't easily test with anonymous classes $class = new class { #[CheckItem('test-check', level: 1)] public function testCheck(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(1, $items); // Second element is the optional check callback (null if not set) $this->assertIsArray($items[0]); } public function testLoadFromClassWithOptionalCheckOnMethod(): void { $class = new class { #[CheckItem('test-check', level: 1)] public function testCheck(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(1, $items); } public function testLoadFromClassSetsCallbackCorrectly(): void { $class = new class { #[CheckItem('test-check', level: 1)] public function testCheck(): string { return 'test-result'; } }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(1, $items); // Test that the callback is set correctly $callback = $items[0][0]->callback; $this->assertIsCallable($callback); $this->assertEquals('test-result', call_user_func($callback)); } public function testLoadFromClassWithBothCheckAndFixItems(): void { $class = new class { #[CheckItem('test-check', level: 1)] public function testCheck(): void {} #[FixItem('test-fix')] public function testFix(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $checkItems = DoctorLoader::getDoctorItems(); $this->assertCount(1, $checkItems); $fixItem = DoctorLoader::getFixItem('test-fix'); $this->assertNotNull($fixItem); } public function testLoadFromClassMultipleTimesAccumulatesItems(): void { $class1 = new class { #[CheckItem('check-1', level: 1)] public function check1(): void {} }; $class2 = new class { #[CheckItem('check-2', level: 2)] public function check2(): void {} }; DoctorLoader::loadFromClass(get_class($class1)); DoctorLoader::loadFromClass(get_class($class2)); $items = DoctorLoader::getDoctorItems(); $this->assertCount(2, $items); } public function testLoadFromPsr4DirLoadsAllClasses(): void { // Create a PSR-4 directory structure $psr4Dir = $this->tempDir . '/DoctorClasses'; mkdir($psr4Dir, 0755, true); // Create test class file 1 $classContent1 = 'assertGreaterThanOrEqual(0, count($items)); } public function testLoadFromPsr4DirSortsItemsByLevel(): void { // Create a PSR-4 directory structure $psr4Dir = $this->tempDir . '/DoctorClasses'; mkdir($psr4Dir, 0755, true); $classContent = '= 2) { $this->assertGreaterThanOrEqual($items[1][0]->level, $items[0][0]->level); } } public function testLoadFromClassIgnoresNonPublicMethods(): void { $class = new class { #[CheckItem('public-check', level: 1)] public function publicCheck(): void {} #[CheckItem('private-check', level: 1)] private function privateCheck(): void {} #[CheckItem('protected-check', level: 1)] protected function protectedCheck(): void {} }; DoctorLoader::loadFromClass(get_class($class)); $items = DoctorLoader::getDoctorItems(); // Should only load public methods $this->assertCount(1, $items); $this->assertEquals('public-check', $items[0][0]->item_name); } public function testLoadFromClassWithNoAttributes(): void { $class = new class { public function regularMethod(): void {} }; DoctorLoader::loadFromClass(get_class($class)); // Should not add any items $items = DoctorLoader::getDoctorItems(); $this->assertEmpty($items); } private function removeDirectory(string $dir): void { if (!is_dir($dir)) { return; } $files = array_diff(scandir($dir), ['.', '..']); foreach ($files as $file) { $path = $dir . '/' . $file; if (is_dir($path)) { $this->removeDirectory($path); } else { unlink($path); } } rmdir($dir); } }