|
| 1 | +<?php |
| 2 | + |
| 3 | +use App\Domains\Mirror\Exceptions\MirrorSyncException; |
| 4 | +use App\Domains\Mirror\Services\RegistryClient\InlineRegistryClient; |
| 5 | +use App\Domains\Mirror\Services\RegistryClient\RegistryClientFactory; |
| 6 | +use App\Domains\Mirror\Services\RegistryClient\V2RegistryClient; |
| 7 | +use App\Models\Mirror; |
| 8 | +use App\Models\Organization; |
| 9 | +use Composer\MetadataMinifier\MetadataMinifier; |
| 10 | +use Illuminate\Support\Facades\Http; |
| 11 | + |
| 12 | +beforeEach(function () { |
| 13 | + $this->organization = Organization::factory()->create(); |
| 14 | + $this->mirror = Mirror::factory()->create([ |
| 15 | + 'organization_uuid' => $this->organization->uuid, |
| 16 | + 'url' => 'https://registry.example.com', |
| 17 | + ]); |
| 18 | +}); |
| 19 | + |
| 20 | +/** |
| 21 | + * Build a minified p2 metadata body, exactly as Pricore itself serves it. |
| 22 | + * |
| 23 | + * @param array<int, array<string, mixed>> $versions |
| 24 | + */ |
| 25 | +function fakeMetadataResponse(string $packageName, array $versions): array |
| 26 | +{ |
| 27 | + return [ |
| 28 | + 'minified' => 'composer/2.0', |
| 29 | + 'packages' => [ |
| 30 | + $packageName => MetadataMinifier::minify($versions), |
| 31 | + ], |
| 32 | + ]; |
| 33 | +} |
| 34 | + |
| 35 | +it('detects the Composer v2 format and prioritizes it over inline packages', function () { |
| 36 | + Http::fake([ |
| 37 | + 'registry.example.com/packages.json' => Http::response([ |
| 38 | + 'metadata-url' => '/p2/%package%.json', |
| 39 | + 'available-packages' => ['vendor/pkg'], |
| 40 | + // A stray v1 "packages" key must not take precedence. |
| 41 | + 'packages' => ['vendor/legacy' => []], |
| 42 | + ]), |
| 43 | + ]); |
| 44 | + |
| 45 | + $client = RegistryClientFactory::make($this->mirror); |
| 46 | + |
| 47 | + expect($client)->toBeInstanceOf(V2RegistryClient::class); |
| 48 | + expect($client->getAvailablePackages())->toBe(['vendor/pkg']); |
| 49 | +}); |
| 50 | + |
| 51 | +it('falls back to the inline v1 client when no metadata-url is present', function () { |
| 52 | + Http::fake([ |
| 53 | + 'registry.example.com/packages.json' => Http::response([ |
| 54 | + 'packages' => [ |
| 55 | + 'vendor/pkg' => [ |
| 56 | + '1.0.0' => ['name' => 'vendor/pkg', 'version' => '1.0.0'], |
| 57 | + ], |
| 58 | + ], |
| 59 | + ]), |
| 60 | + ]); |
| 61 | + |
| 62 | + expect(RegistryClientFactory::make($this->mirror)) |
| 63 | + ->toBeInstanceOf(InlineRegistryClient::class); |
| 64 | +}); |
| 65 | + |
| 66 | +it('fetches and expands both stable and dev versions', function () { |
| 67 | + Http::fake([ |
| 68 | + 'registry.example.com/packages.json' => Http::response([ |
| 69 | + 'metadata-url' => '/p2/%package%.json', |
| 70 | + 'available-packages' => ['vendor/pkg'], |
| 71 | + ]), |
| 72 | + 'registry.example.com/p2/vendor/pkg.json' => Http::response(fakeMetadataResponse('vendor/pkg', [ |
| 73 | + ['name' => 'vendor/pkg', 'version' => '2.0.0', 'version_normalized' => '2.0.0.0', 'dist' => ['reference' => 'ref-2', 'url' => 'https://registry.example.com/d/2.zip']], |
| 74 | + ['name' => 'vendor/pkg', 'version' => '1.0.0', 'version_normalized' => '1.0.0.0', 'dist' => ['reference' => 'ref-1', 'url' => 'https://registry.example.com/d/1.zip']], |
| 75 | + ])), |
| 76 | + 'registry.example.com/p2/vendor/pkg~dev.json' => Http::response(fakeMetadataResponse('vendor/pkg', [ |
| 77 | + ['name' => 'vendor/pkg', 'version' => 'dev-main', 'version_normalized' => 'dev-main', 'dist' => ['reference' => 'ref-dev']], |
| 78 | + ])), |
| 79 | + ]); |
| 80 | + |
| 81 | + $versions = RegistryClientFactory::make($this->mirror)->getPackageVersions('vendor/pkg'); |
| 82 | + |
| 83 | + expect(array_keys($versions))->toEqualCanonicalizing(['1.0.0', '2.0.0', 'dev-main']); |
| 84 | + // Expansion must restore each version's own dist reference (not leak the diff). |
| 85 | + expect($versions['2.0.0']['dist']['reference'])->toBe('ref-2'); |
| 86 | + expect($versions['1.0.0']['dist']['reference'])->toBe('ref-1'); |
| 87 | + expect($versions['dev-main']['dist']['reference'])->toBe('ref-dev'); |
| 88 | +}); |
| 89 | + |
| 90 | +it('ignores a missing dev metadata file', function () { |
| 91 | + Http::fake([ |
| 92 | + 'registry.example.com/packages.json' => Http::response([ |
| 93 | + 'metadata-url' => '/p2/%package%.json', |
| 94 | + 'available-packages' => ['vendor/pkg'], |
| 95 | + ]), |
| 96 | + 'registry.example.com/p2/vendor/pkg.json' => Http::response(fakeMetadataResponse('vendor/pkg', [ |
| 97 | + ['name' => 'vendor/pkg', 'version' => '1.0.0', 'version_normalized' => '1.0.0.0', 'dist' => ['reference' => 'ref-1']], |
| 98 | + ])), |
| 99 | + 'registry.example.com/p2/vendor/pkg~dev.json' => Http::response('Not Found', 404), |
| 100 | + ]); |
| 101 | + |
| 102 | + $versions = RegistryClientFactory::make($this->mirror)->getPackageVersions('vendor/pkg'); |
| 103 | + |
| 104 | + expect(array_keys($versions))->toBe(['1.0.0']); |
| 105 | +}); |
| 106 | + |
| 107 | +it('throws when a v2 registry does not advertise available-packages', function () { |
| 108 | + Http::fake([ |
| 109 | + 'registry.example.com/packages.json' => Http::response([ |
| 110 | + 'metadata-url' => '/p2/%package%.json', |
| 111 | + 'list' => '/packages/list.json', |
| 112 | + ]), |
| 113 | + ]); |
| 114 | + |
| 115 | + RegistryClientFactory::make($this->mirror); |
| 116 | +})->throws(MirrorSyncException::class, 'available-packages'); |
0 commit comments