getProperty('artifact_configs'); $property->setAccessible(true); $property->setValue(null, []); $loaderReflection = new \ReflectionClass(ArtifactLoader::class); $loaderProperty = $loaderReflection->getProperty('artifacts'); $loaderProperty->setAccessible(true); $loaderProperty->setValue(null, null); } protected function tearDown(): void { parent::tearDown(); $reflection = new \ReflectionClass(ArtifactConfig::class); $property = $reflection->getProperty('artifact_configs'); $property->setAccessible(true); $property->setValue(null, []); $loaderReflection = new \ReflectionClass(ArtifactLoader::class); $loaderProperty = $loaderReflection->getProperty('artifacts'); $loaderProperty->setAccessible(true); $loaderProperty->setValue(null, null); } // ==================== DOWNLOADERS constant ==================== public function testDownloadersConstantHasExpectedKeys(): void { $expectedKeys = ['bitbuckettag', 'filelist', 'git', 'ghrel', 'ghtar', 'ghtagtar', 'local', 'pie', 'pecl', 'url', 'php-release', 'hosted']; foreach ($expectedKeys as $key) { $this->assertArrayHasKey($key, ArtifactDownloader::DOWNLOADERS, "Missing downloader key: {$key}"); } } // ==================== Constructor options ==================== public function testConstructWithDefaultOptions(): void { $downloader = new ArtifactDownloader([], false); $this->assertSame(0, $downloader->getRetry()); $this->assertFalse($downloader->interactive); $this->assertEmpty($downloader->getArtifacts()); } public function testConstructWithParallelOption(): void { $downloader = new ArtifactDownloader(['parallel' => 4], false); // parallel is internal but setParallel/getArtifacts reveals behavior; check via setParallel chainability // Indirect verification: setParallel with same value returns $this $this->assertSame($downloader, $downloader->setParallel(4)); } public function testConstructWithRetryOption(): void { $downloader = new ArtifactDownloader(['retry' => 3], false); $this->assertSame(3, $downloader->getRetry()); } public function testConstructWithNegativeRetryClampedToZero(): void { $downloader = new ArtifactDownloader(['retry' => -5], false); $this->assertSame(0, $downloader->getRetry()); } public function testConstructWithPreferSourceBoolOption(): void { // prefer-source=true sets default to FETCH_PREFER_SOURCE (0) $downloader = new ArtifactDownloader(['prefer-source' => true], false); $this->assertSame(0, $downloader->getRetry()); // sanity check, object created fine } public function testConstructWithPreferBinaryBoolOption(): void { $downloader = new ArtifactDownloader(['prefer-binary' => true], false); $this->assertNotNull($downloader); } public function testConstructWithPreferPreBuiltBoolOption(): void { $downloader = new ArtifactDownloader(['prefer-pre-built' => true], false); $this->assertNotNull($downloader); } public function testConstructWithSourceOnlyBoolOption(): void { $downloader = new ArtifactDownloader(['source-only' => true], false); $this->assertNotNull($downloader); } public function testConstructWithBinaryOnlyBoolOption(): void { $downloader = new ArtifactDownloader(['binary-only' => true], false); $this->assertNotNull($downloader); } public function testConstructWithIgnoreCacheBoolOption(): void { $downloader = new ArtifactDownloader(['ignore-cache' => true], false); $this->assertNotNull($downloader); } public function testConstructWithIgnoreCacheStringOptionParsesNames(): void { $downloader = new ArtifactDownloader(['ignore-cache' => 'openssl,zlib'], false); $this->assertNotNull($downloader); } public function testConstructWithIgnoreCacheSourcesBackwardCompat(): void { $downloader = new ArtifactDownloader(['ignore-cache-sources' => true], false); $this->assertNotNull($downloader); } public function testConstructWithCustomUrlOptionAddsToIgnoreCache(): void { $downloader = new ArtifactDownloader( ['custom-url' => ['openssl:https://custom.example.com/openssl.tar.gz']], false ); $this->assertNotNull($downloader); } public function testConstructWithCustomGitOption(): void { $downloader = new ArtifactDownloader( ['custom-git' => ['php-src:master:https://github.com/php/php-src.git']], false ); $this->assertNotNull($downloader); } public function testConstructWithCustomLocalOption(): void { $downloader = new ArtifactDownloader( ['custom-local' => ['my-lib:/tmp/my-lib-source']], false ); $this->assertNotNull($downloader); } public function testConstructWithNoAltOption(): void { $downloader = new ArtifactDownloader(['no-alt' => true], false); $this->assertNotNull($downloader); } // ==================== getRetry ==================== public function testGetRetryDefaultsToZero(): void { $downloader = new ArtifactDownloader([], false); $this->assertSame(0, $downloader->getRetry()); } public function testGetRetryReturnsConfiguredValue(): void { $downloader = new ArtifactDownloader(['retry' => 5], false); $this->assertSame(5, $downloader->getRetry()); } // ==================== getOption ==================== public function testGetOptionReturnsConfiguredValue(): void { $downloader = new ArtifactDownloader(['retry' => 2, 'parallel' => 3], false); $this->assertSame(2, $downloader->getOption('retry')); $this->assertSame(3, $downloader->getOption('parallel')); } public function testGetOptionReturnsDefaultWhenNotSet(): void { $downloader = new ArtifactDownloader([], false); $this->assertNull($downloader->getOption('non-existent')); $this->assertSame('default-val', $downloader->getOption('non-existent', 'default-val')); } // ==================== getArtifacts ==================== public function testGetArtifactsReturnsEmptyInitially(): void { $downloader = new ArtifactDownloader([], false); $this->assertSame([], $downloader->getArtifacts()); } // ==================== add ==================== public function testAddWithArtifactObjectAddsToList(): void { $downloader = new ArtifactDownloader([], false); $artifact = new Artifact('my-pkg', ['source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz']]); $downloader->add($artifact); $artifacts = $downloader->getArtifacts(); $this->assertArrayHasKey('my-pkg', $artifacts); $this->assertSame($artifact, $artifacts['my-pkg']); } public function testAddReturnsSelf(): void { $downloader = new ArtifactDownloader([], false); $artifact = new Artifact('my-pkg', ['source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz']]); $result = $downloader->add($artifact); $this->assertSame($downloader, $result); } public function testAddDoesNotAddDuplicateArtifact(): void { $downloader = new ArtifactDownloader([], false); $artifact = new Artifact('my-pkg', ['source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz']]); $downloader->add($artifact); $downloader->add($artifact); $this->assertCount(1, $downloader->getArtifacts()); } public function testAddWithStringNameLooksUpFromArtifactLoader(): void { $this->injectArtifactConfig('my-lib', ['source' => ['type' => 'url', 'url' => 'https://example.com/file.tar.gz']]); $downloader = new ArtifactDownloader([], false); $downloader->add('my-lib'); $artifacts = $downloader->getArtifacts(); $this->assertArrayHasKey('my-lib', $artifacts); } public function testAddWithStringNameThrowsForNonExistentArtifact(): void { $downloader = new ArtifactDownloader([], false); $this->expectException(WrongUsageException::class); $this->expectExceptionMessage("Artifact 'non-existent' not found"); $downloader->add('non-existent'); } // ==================== addArtifacts ==================== public function testAddArtifactsAddsMultipleAtOnce(): void { $downloader = new ArtifactDownloader([], false); $a1 = new Artifact('pkg-a', ['source' => ['type' => 'url', 'url' => 'https://example.com/a.tar.gz']]); $a2 = new Artifact('pkg-b', ['source' => ['type' => 'url', 'url' => 'https://example.com/b.tar.gz']]); $downloader->addArtifacts([$a1, $a2]); $this->assertCount(2, $downloader->getArtifacts()); $this->assertArrayHasKey('pkg-a', $downloader->getArtifacts()); $this->assertArrayHasKey('pkg-b', $downloader->getArtifacts()); } public function testAddArtifactsReturnsSelf(): void { $downloader = new ArtifactDownloader([], false); $result = $downloader->addArtifacts([]); $this->assertSame($downloader, $result); } // ==================== setParallel ==================== public function testSetParallelReturnsSelf(): void { $downloader = new ArtifactDownloader([], false); $result = $downloader->setParallel(3); $this->assertSame($downloader, $result); } public function testSetParallelEnforcesMinimumOfOne(): void { $downloader = new ArtifactDownloader([], false); $downloader->setParallel(0); // No direct getter for parallel, but verifying it doesn't throw $this->assertSame($downloader, $downloader->setParallel(0)); } public function testSetParallelAcceptsNormalValue(): void { $downloader = new ArtifactDownloader([], false); $result = $downloader->setParallel(5); $this->assertSame($downloader, $result); } // ==================== Helpers ==================== private function injectArtifactConfig(string $name, array $config): void { $reflection = new \ReflectionClass(ArtifactConfig::class); $property = $reflection->getProperty('artifact_configs'); $property->setAccessible(true); $configs = $property->getValue(null) ?? []; $configs[$name] = $config; $property->setValue(null, $configs); } }