From 2e70f7b4fb5587a5b70845ab711b42556a4059dc Mon Sep 17 00:00:00 2001 From: Stephan Vock Date: Fri, 7 Nov 2025 12:06:11 +0000 Subject: [PATCH 1/3] Client: handle pagination, default to 500 items per page, automatically paginate additional pages --- src/Api/AbstractApi.php | 2 + src/Api/Credentials.php | 2 +- src/Api/Customers.php | 4 +- src/Api/Customers/MagentoLegacyKeys.php | 2 +- src/Api/Customers/VendorBundles.php | 2 +- src/Api/MirroredRepositories.php | 4 +- src/Api/Packages.php | 8 +- src/Api/Packages/Artifacts.php | 2 +- src/Api/SecurityIssues.php | 2 + src/Api/Suborganizations.php | 6 +- .../Suborganizations/MirroredRepositories.php | 4 +- src/Api/Suborganizations/Packages.php | 4 +- src/Api/Subrepositories.php | 8 +- .../Subrepositories/MirroredRepositories.php | 4 +- src/Api/Subrepositories/Packages.php | 4 +- src/Api/Synchronizations.php | 2 +- src/Api/Teams.php | 4 +- src/Api/Tokens.php | 2 +- src/Api/VendorBundles.php | 2 +- src/Api/VendorBundles/Packages.php | 2 +- src/Client.php | 2 + src/HttpClient/HttpPluginClientBuilder.php | 18 +++- src/HttpClient/Plugin/AutoPaginator.php | 85 +++++++++++++++++++ tests/Api/CredentialsTest.php | 2 +- tests/Api/Customers/MagentoLegacyKeysTest.php | 3 +- tests/Api/Customers/VendorBundlesTest.php | 3 +- tests/Api/CustomersTest.php | 4 +- tests/Api/MirroredRepositoriesTest.php | 4 +- tests/Api/Packages/ArtifactsTest.php | 3 +- tests/Api/PackagesTest.php | 13 ++- .../Api/Projects/MirroredRepositoriesTest.php | 5 +- tests/Api/Projects/PackagesTest.php | 10 ++- tests/Api/ProjectsTest.php | 6 +- tests/Api/SecurityIssuesTest.php | 7 +- .../MirroredRepositoriesTest.php | 5 +- tests/Api/Suborganizations/PackagesTest.php | 12 ++- tests/Api/SuborganizationsTest.php | 6 +- .../MirroredRepositoriesTest.php | 5 +- tests/Api/Subrepositories/PackagesTest.php | 12 ++- tests/Api/SubrepositoriesTest.php | 6 +- tests/Api/SynchronizationsTest.php | 2 +- tests/Api/TeamsTest.php | 7 +- tests/Api/TokensTest.php | 2 +- tests/Api/VendorBundles/PackagesTest.php | 3 +- tests/Api/VendorBundlesTest.php | 2 +- tests/HttpClient/Plugin/AutoPaginatorTest.php | 85 +++++++++++++++++++ .../TrustedPublishingTokenExchangeTest.php | 3 +- 47 files changed, 309 insertions(+), 76 deletions(-) create mode 100644 src/HttpClient/Plugin/AutoPaginator.php create mode 100644 tests/HttpClient/Plugin/AutoPaginatorTest.php diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php index a923eef..f1dc9da 100644 --- a/src/Api/AbstractApi.php +++ b/src/Api/AbstractApi.php @@ -14,6 +14,8 @@ abstract class AbstractApi { + const DEFAULT_LIMIT = 500; + /** @var Client */ protected $client; /** @var ResponseMediator */ diff --git a/src/Api/Credentials.php b/src/Api/Credentials.php index 5d7c103..d40ddc0 100644 --- a/src/Api/Credentials.php +++ b/src/Api/Credentials.php @@ -21,7 +21,7 @@ class Credentials extends AbstractApi public function all() { - return $this->get('/credentials/'); + return $this->get('/credentials/', ['limit' => self::DEFAULT_LIMIT]); } public function show($credentialId) diff --git a/src/Api/Customers.php b/src/Api/Customers.php index 237d079..f86e195 100644 --- a/src/Api/Customers.php +++ b/src/Api/Customers.php @@ -16,7 +16,7 @@ class Customers extends AbstractApi { public function all() { - return $this->get('/customers/'); + return $this->get('/customers/', ['limit' => self::DEFAULT_LIMIT]); } public function show($customerIdOrUrlName) @@ -70,7 +70,7 @@ public function disable($customerIdOrUrlName) public function listPackages($customerIdOrUrlName) { - return $this->get(sprintf('/customers/%s/packages/', $customerIdOrUrlName)); + return $this->get(sprintf('/customers/%s/packages/', $customerIdOrUrlName), ['limit' => self::DEFAULT_LIMIT]); } public function showPackage($customerIdOrUrlName, $packageIdOrName) diff --git a/src/Api/Customers/MagentoLegacyKeys.php b/src/Api/Customers/MagentoLegacyKeys.php index 4d35cfa..a2e1041 100644 --- a/src/Api/Customers/MagentoLegacyKeys.php +++ b/src/Api/Customers/MagentoLegacyKeys.php @@ -15,7 +15,7 @@ class MagentoLegacyKeys extends AbstractApi { public function all($customerIdOrUrlName) { - return $this->get(sprintf('/api/customers/%s/magento-legacy-keys/', $customerIdOrUrlName)); + return $this->get(sprintf('/api/customers/%s/magento-legacy-keys/', $customerIdOrUrlName), ['limit' => self::DEFAULT_LIMIT]); } public function create($customerIdOrUrlName, $publicKey, $privateKey) diff --git a/src/Api/Customers/VendorBundles.php b/src/Api/Customers/VendorBundles.php index c8fda8a..3e2893f 100644 --- a/src/Api/Customers/VendorBundles.php +++ b/src/Api/Customers/VendorBundles.php @@ -15,7 +15,7 @@ class VendorBundles extends AbstractApi { public function listVendorBundles($customerIdOrUrlName) { - return $this->get(sprintf('/customers/%s/vendor-bundles/', $customerIdOrUrlName)); + return $this->get(sprintf('/customers/%s/vendor-bundles/', $customerIdOrUrlName), ['limit' => self::DEFAULT_LIMIT]); } /** diff --git a/src/Api/MirroredRepositories.php b/src/Api/MirroredRepositories.php index fe60093..ca68ca1 100644 --- a/src/Api/MirroredRepositories.php +++ b/src/Api/MirroredRepositories.php @@ -13,7 +13,7 @@ class MirroredRepositories extends AbstractApi { public function all() { - return $this->get('/mirrored-repositories/'); + return $this->get('/mirrored-repositories/', ['limit' => self::DEFAULT_LIMIT]); } public function create($name, $url, $mirroringBehavior, $credentials = null) @@ -48,7 +48,7 @@ public function remove($mirroredRepositoryId) public function listPackages($mirroredRepositoryId) { - return $this->get(sprintf('/mirrored-repositories/%s/packages/', $mirroredRepositoryId)); + return $this->get(sprintf('/mirrored-repositories/%s/packages/', $mirroredRepositoryId), ['limit' => self::DEFAULT_LIMIT]); } public function addPackages($mirroredRepositoryId, array $packages) diff --git a/src/Api/Packages.php b/src/Api/Packages.php index 6a1f1a0..a1bc543 100644 --- a/src/Api/Packages.php +++ b/src/Api/Packages.php @@ -52,6 +52,8 @@ public function all(array $filters = []) throw new InvalidArgumentException('Filter "origin" has to be one of: "' . implode('", "', self::AVAILABLE_ORIGINS) . '".'); } + $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); + return $this->get('/packages/', $filters); } @@ -127,16 +129,18 @@ public function remove($packageIdOrName) public function listCustomers($packageIdOrName) { - return $this->get(sprintf('/packages/%s/customers/', $packageIdOrName)); + return $this->get(sprintf('/packages/%s/customers/', $packageIdOrName), ['limit' => self::DEFAULT_LIMIT]); } public function listDependents($packageName) { - return $this->get(sprintf('/packages/%s/dependents/', $packageName)); + return $this->get(sprintf('/packages/%s/dependents/', $packageName), ['limit' => self::DEFAULT_LIMIT]); } public function listSecurityIssues($packageIdOrName, array $filters = []) { + $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); + return $this->get(sprintf('/packages/%s/security-issues/', $packageIdOrName), $filters); } diff --git a/src/Api/Packages/Artifacts.php b/src/Api/Packages/Artifacts.php index 01b4975..26ff0b5 100644 --- a/src/Api/Packages/Artifacts.php +++ b/src/Api/Packages/Artifacts.php @@ -36,6 +36,6 @@ public function show($artifactId) public function showPackageArtifacts($packageIdOrName) { - return $this->get(sprintf('/packages/%s/artifacts/', $packageIdOrName)); + return $this->get(sprintf('/packages/%s/artifacts/', $packageIdOrName), ['limit' => self::DEFAULT_LIMIT]); } } diff --git a/src/Api/SecurityIssues.php b/src/Api/SecurityIssues.php index e6f7f12..c915521 100644 --- a/src/Api/SecurityIssues.php +++ b/src/Api/SecurityIssues.php @@ -48,6 +48,8 @@ class SecurityIssues extends AbstractApi public function all(array $filters = []) { + $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); + return $this->get('/security-issues/', $filters); } diff --git a/src/Api/Suborganizations.php b/src/Api/Suborganizations.php index 614ad9a..e9a14cf 100644 --- a/src/Api/Suborganizations.php +++ b/src/Api/Suborganizations.php @@ -15,7 +15,7 @@ class Suborganizations extends AbstractApi { public function all() { - return $this->get('/suborganizations/'); + return $this->get('/suborganizations/', ['limit' => self::DEFAULT_LIMIT]); } public function show($suborganizationName) @@ -35,7 +35,7 @@ public function remove($suborganizationName) public function listTeams($suborganizationName) { - return $this->get(sprintf('/suborganizations/%s/teams/', $suborganizationName)); + return $this->get(sprintf('/suborganizations/%s/teams/', $suborganizationName), ['limit' => self::DEFAULT_LIMIT]); } public function addOrEditTeams($suborganizationName, array $teams) @@ -60,7 +60,7 @@ public function removeTeam($suborganizationName, $teamId) public function listTokens($suborganizationName) { - return $this->get(sprintf('/suborganizations/%s/tokens/', $suborganizationName)); + return $this->get(sprintf('/suborganizations/%s/tokens/', $suborganizationName), ['limit' => self::DEFAULT_LIMIT]); } public function createToken($suborganizationName, array $tokenData) diff --git a/src/Api/Suborganizations/MirroredRepositories.php b/src/Api/Suborganizations/MirroredRepositories.php index 6230369..d38abdc 100644 --- a/src/Api/Suborganizations/MirroredRepositories.php +++ b/src/Api/Suborganizations/MirroredRepositories.php @@ -16,7 +16,7 @@ class MirroredRepositories extends AbstractApi { public function all($suborganizationName) { - return $this->get(sprintf('/suborganizations/%s/mirrored-repositories/', $suborganizationName)); + return $this->get(sprintf('/suborganizations/%s/mirrored-repositories/', $suborganizationName), ['limit' => self::DEFAULT_LIMIT]); } public function add($suborganizationName, array $mirroredRepositories) @@ -49,7 +49,7 @@ public function remove($suborganizationName, $mirroredRepositoryId) public function listPackages($suborganizationName, $mirroredRepositoryId) { - return $this->get(sprintf('/suborganizations/%s/mirrored-repositories/%s/packages/', $suborganizationName, $mirroredRepositoryId)); + return $this->get(sprintf('/suborganizations/%s/mirrored-repositories/%s/packages/', $suborganizationName, $mirroredRepositoryId), ['limit' => self::DEFAULT_LIMIT]); } public function addPackages($suborganizationName, $mirroredRepositoryId, array $packages) diff --git a/src/Api/Suborganizations/Packages.php b/src/Api/Suborganizations/Packages.php index b2e02d9..c994b48 100644 --- a/src/Api/Suborganizations/Packages.php +++ b/src/Api/Suborganizations/Packages.php @@ -22,6 +22,8 @@ public function all($suborganizationName, array $filters = []) throw new InvalidArgumentException('Filter "origin" has to be one of: "' . implode('", "', \PrivatePackagist\ApiClient\Api\Packages::AVAILABLE_ORIGINS) . '".'); } + $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); + return $this->get(sprintf('/suborganizations/%s/packages/', $suborganizationName), $filters); } @@ -65,6 +67,6 @@ public function remove($suborganizationName, $packageIdOrName) public function listDependents($suborganizationName, $packageIdOrName) { - return $this->get(sprintf('/suborganizations/%s/packages/%s/dependents/', $suborganizationName, $packageIdOrName)); + return $this->get(sprintf('/suborganizations/%s/packages/%s/dependents/', $suborganizationName, $packageIdOrName), ['limit' => self::DEFAULT_LIMIT]); } } diff --git a/src/Api/Subrepositories.php b/src/Api/Subrepositories.php index 1e3d108..2fc1d75 100644 --- a/src/Api/Subrepositories.php +++ b/src/Api/Subrepositories.php @@ -18,7 +18,7 @@ class Subrepositories extends AbstractApi { public function all() { - return $this->get('/subrepositories/'); + return $this->get('/subrepositories/', ['limit' => self::DEFAULT_LIMIT]); } public function show($subrepositoryName) @@ -38,7 +38,7 @@ public function remove($subrepositoryName) public function listTeams($subrepositoryName) { - return $this->get(sprintf('/subrepositories/%s/teams/', $subrepositoryName)); + return $this->get(sprintf('/subrepositories/%s/teams/', $subrepositoryName), ['limit' => self::DEFAULT_LIMIT]); } /** @@ -76,12 +76,12 @@ public function removeTeam($subrepositoryName, $teamId) #[\Deprecated('Use Subrepositories::packages()->all() instead', '1.16.1')] public function listPackages($subrepositoryName) { - return $this->packages()->all($subrepositoryName); + return $this->packages()->all($subrepositoryName, ['limit' => self::DEFAULT_LIMIT]); } public function listTokens($subrepositoryName) { - return $this->get(sprintf('/subrepositories/%s/tokens/', $subrepositoryName)); + return $this->get(sprintf('/subrepositories/%s/tokens/', $subrepositoryName), ['limit' => self::DEFAULT_LIMIT]); } public function createToken($subrepositoryName, array $tokenData) diff --git a/src/Api/Subrepositories/MirroredRepositories.php b/src/Api/Subrepositories/MirroredRepositories.php index 3875f8a..f356971 100644 --- a/src/Api/Subrepositories/MirroredRepositories.php +++ b/src/Api/Subrepositories/MirroredRepositories.php @@ -19,7 +19,7 @@ class MirroredRepositories extends AbstractApi { public function all($subrepositoryName) { - return $this->get(sprintf('/subrepositories/%s/mirrored-repositories/', $subrepositoryName)); + return $this->get(sprintf('/subrepositories/%s/mirrored-repositories/', $subrepositoryName), ['limit' => self::DEFAULT_LIMIT]); } public function add($subrepositoryName, array $mirroredRepositories) @@ -52,7 +52,7 @@ public function remove($subrepositoryName, $mirroredRepositoryId) public function listPackages($subrepositoryName, $mirroredRepositoryId) { - return $this->get(sprintf('/subrepositories/%s/mirrored-repositories/%s/packages/', $subrepositoryName, $mirroredRepositoryId)); + return $this->get(sprintf('/subrepositories/%s/mirrored-repositories/%s/packages/', $subrepositoryName, $mirroredRepositoryId), ['limit' => self::DEFAULT_LIMIT]); } public function addPackages($subrepositoryName, $mirroredRepositoryId, array $packages) diff --git a/src/Api/Subrepositories/Packages.php b/src/Api/Subrepositories/Packages.php index 6faa4bf..8f18f59 100644 --- a/src/Api/Subrepositories/Packages.php +++ b/src/Api/Subrepositories/Packages.php @@ -25,6 +25,8 @@ public function all($subrepositoryName, array $filters = []) throw new InvalidArgumentException('Filter "origin" has to be one of: "' . implode('", "', \PrivatePackagist\ApiClient\Api\Packages::AVAILABLE_ORIGINS) . '".'); } + $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); + return $this->get(sprintf('/subrepositories/%s/packages/', $subrepositoryName), $filters); } @@ -68,6 +70,6 @@ public function remove($subrepositoryName, $packageIdOrName) public function listDependents($subrepositoryName, $packageIdOrName) { - return $this->get(sprintf('/subrepositories/%s/packages/%s/dependents/', $subrepositoryName, $packageIdOrName)); + return $this->get(sprintf('/subrepositories/%s/packages/%s/dependents/', $subrepositoryName, $packageIdOrName), ['limit' => self::DEFAULT_LIMIT]); } } diff --git a/src/Api/Synchronizations.php b/src/Api/Synchronizations.php index b18f4c9..94fe183 100644 --- a/src/Api/Synchronizations.php +++ b/src/Api/Synchronizations.php @@ -13,6 +13,6 @@ class Synchronizations extends AbstractApi { public function all() { - return $this->get('/synchronizations/'); + return $this->get('/synchronizations/', ['limit' => self::DEFAULT_LIMIT]); } } diff --git a/src/Api/Teams.php b/src/Api/Teams.php index c0aeebb..ca84b11 100644 --- a/src/Api/Teams.php +++ b/src/Api/Teams.php @@ -15,7 +15,7 @@ class Teams extends AbstractApi { public function all() { - return $this->get('/teams/'); + return $this->get('/teams/', ['limit' => self::DEFAULT_LIMIT]); } public function create(string $name, TeamPermissions $permissions): array @@ -82,7 +82,7 @@ public function removeMember($teamId, $userId): array public function packages($teamId) { - return $this->get(sprintf('/teams/%s/packages/', $teamId)); + return $this->get(sprintf('/teams/%s/packages/', $teamId), ['limit' => self::DEFAULT_LIMIT]); } public function addPackages($teamId, array $packages) diff --git a/src/Api/Tokens.php b/src/Api/Tokens.php index 0cf20ab..56fd98d 100644 --- a/src/Api/Tokens.php +++ b/src/Api/Tokens.php @@ -15,7 +15,7 @@ class Tokens extends AbstractApi { public function all() { - return $this->get('/tokens/'); + return $this->get('/tokens/', ['limit' => self::DEFAULT_LIMIT]); } public function create(array $tokenData) diff --git a/src/Api/VendorBundles.php b/src/Api/VendorBundles.php index 8960bdd..4280736 100644 --- a/src/Api/VendorBundles.php +++ b/src/Api/VendorBundles.php @@ -16,7 +16,7 @@ class VendorBundles extends AbstractApi */ public function all() { - return $this->get('/vendor-bundles/'); + return $this->get('/vendor-bundles/', ['limit' => self::DEFAULT_LIMIT]); } /** diff --git a/src/Api/VendorBundles/Packages.php b/src/Api/VendorBundles/Packages.php index 4b83c26..3aa9d94 100644 --- a/src/Api/VendorBundles/Packages.php +++ b/src/Api/VendorBundles/Packages.php @@ -20,7 +20,7 @@ class Packages extends AbstractApi */ public function listPackages($vendorBundleIds) { - return $this->get(sprintf('/vendor-bundles/%s/packages/', $vendorBundleIds)); + return $this->get(sprintf('/vendor-bundles/%s/packages/', $vendorBundleIds), ['limit' => self::DEFAULT_LIMIT]); } /** diff --git a/src/Client.php b/src/Client.php index 5d532d1..79c0ebf 100644 --- a/src/Client.php +++ b/src/Client.php @@ -13,6 +13,7 @@ use Http\Discovery\Psr17FactoryDiscovery; use PrivatePackagist\ApiClient\HttpClient\HttpPluginClientBuilder; use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; +use PrivatePackagist\ApiClient\HttpClient\Plugin\AutoPaginator; use PrivatePackagist\ApiClient\HttpClient\Plugin\ExceptionThrower; use PrivatePackagist\ApiClient\HttpClient\Plugin\PathPrepend; use PrivatePackagist\ApiClient\HttpClient\Plugin\RequestSignature; @@ -49,6 +50,7 @@ public function __construct(?HttpPluginClientBuilder $httpClientBuilder = null, } $builder->addPlugin(new Plugin\HeaderDefaultsPlugin($headers)); $builder->addPlugin(new ExceptionThrower($this->responseMediator)); + $builder->addPlugin(new AutoPaginator($httpClientBuilder->getRequestFactory(), $httpClientBuilder->getStreamFactory(), $this->responseMediator)); } /** diff --git a/src/HttpClient/HttpPluginClientBuilder.php b/src/HttpClient/HttpPluginClientBuilder.php index 098322e..8c56c39 100644 --- a/src/HttpClient/HttpPluginClientBuilder.php +++ b/src/HttpClient/HttpPluginClientBuilder.php @@ -39,7 +39,7 @@ class HttpPluginClientBuilder public function __construct( ?ClientInterface $httpClient = null, $requestFactory = null, - ?StreamFactoryInterface $streamFactory= null + ?StreamFactoryInterface $streamFactory = null ) { $requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); if ($requestFactory instanceof RequestFactory) { @@ -107,4 +107,20 @@ public function getHttpClientWithoutPlugins(): HttpMethodsClient $this->streamFactory ); } + + /** + * @return RequestFactory|RequestFactoryInterface + */ + public function getRequestFactory() + { + return $this->requestFactory; + } + + /** + * @return StreamFactoryInterface + */ + public function getStreamFactory(): StreamFactoryInterface + { + return $this->streamFactory; + } } diff --git a/src/HttpClient/Plugin/AutoPaginator.php b/src/HttpClient/Plugin/AutoPaginator.php new file mode 100644 index 0000000..e72c7ba --- /dev/null +++ b/src/HttpClient/Plugin/AutoPaginator.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PrivatePackagist\ApiClient\HttpClient\Plugin; + +use Composer\Pcre\Preg; +use Http\Client\Common\Plugin; +use Http\Message\RequestFactory; +use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamFactoryInterface; + +class AutoPaginator implements Plugin +{ + use Plugin\VersionBridgePlugin; + + /** @var RequestFactory|RequestFactoryInterface */ + private $requestFactory; + /** @var StreamFactoryInterface */ + private $streamFactory; + /** @var ResponseMediator */ + private $responseMediator; + + /** + * @param RequestFactory|RequestFactoryInterface $requestFactory + */ + public function __construct( + $requestFactory, + StreamFactoryInterface $streamFactory, + ResponseMediator $responseMediator + ) { + $this->requestFactory = $requestFactory; + $this->streamFactory = $streamFactory; + $this->responseMediator = $responseMediator; + } + + protected function doHandleRequest(RequestInterface $request, callable $next, callable $first) + { + return $next($request)->then(function (ResponseInterface $response) use ($first) { + if (!$response->hasHeader('Link')) { + return $response; + } + + $next = $this->parseLinkHeader($response->getHeaderLine('Link'), 'next'); + if (!$next) { + return $response; + } + + $nextResponse = $first($this->requestFactory->createRequest('GET', $next))->wait(); + + if ($nextResponse->getStatusCode() !== 200) { + return $nextResponse; + } + + return $response + ->withoutHeader('Link') + ->withBody($this->streamFactory->createStream(json_encode(array_merge( + $this->responseMediator->getContent($response), + $this->responseMediator->getContent($nextResponse) + )))); + }); + } + + private function parseLinkHeader(string $header, string $type): ?string + { + foreach (explode(',', $header) as $relation) { + if (Preg::isMatch('/<(.*)>; rel="(.*)"/i', \trim($relation, ','), $match)) { + /** @var string[] $match */ + if (3 === count($match) && $match[2] === $type) { + return $match[1]; + } + } + } + + return null; + } +} diff --git a/tests/Api/CredentialsTest.php b/tests/Api/CredentialsTest.php index 6d29893..d1297a4 100644 --- a/tests/Api/CredentialsTest.php +++ b/tests/Api/CredentialsTest.php @@ -30,7 +30,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/credentials/')) + ->with($this->equalTo('/credentials/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); diff --git a/tests/Api/Customers/MagentoLegacyKeysTest.php b/tests/Api/Customers/MagentoLegacyKeysTest.php index aa62dc6..317c1a9 100644 --- a/tests/Api/Customers/MagentoLegacyKeysTest.php +++ b/tests/Api/Customers/MagentoLegacyKeysTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Customers; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class MagentoLegacyKeysTest extends ApiTestCase @@ -27,7 +28,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/api/customers/13/magento-legacy-keys/')) + ->with($this->equalTo('/api/customers/13/magento-legacy-keys/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all(13)); diff --git a/tests/Api/Customers/VendorBundlesTest.php b/tests/Api/Customers/VendorBundlesTest.php index d760c35..0b68e13 100644 --- a/tests/Api/Customers/VendorBundlesTest.php +++ b/tests/Api/Customers/VendorBundlesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Customers; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class VendorBundlesTest extends ApiTestCase @@ -27,7 +28,7 @@ public function testListVendorBundles() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/customers/1/vendor-bundles/')) + ->with($this->equalTo('/customers/1/vendor-bundles/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listVendorBundles(1)); diff --git a/tests/Api/CustomersTest.php b/tests/Api/CustomersTest.php index 425743f..6ae0d6e 100644 --- a/tests/Api/CustomersTest.php +++ b/tests/Api/CustomersTest.php @@ -29,7 +29,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/customers/')) + ->with($this->equalTo('/customers/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -205,7 +205,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/customers/1/packages/')) + ->with($this->equalTo('/customers/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages(1)); diff --git a/tests/Api/MirroredRepositoriesTest.php b/tests/Api/MirroredRepositoriesTest.php index 79b0086..9e53a31 100644 --- a/tests/Api/MirroredRepositoriesTest.php +++ b/tests/Api/MirroredRepositoriesTest.php @@ -23,7 +23,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/mirrored-repositories/')) + ->with($this->equalTo('/mirrored-repositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -97,7 +97,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/mirrored-repositories/1/packages/')) + ->with($this->equalTo('/mirrored-repositories/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages(1)); diff --git a/tests/Api/Packages/ArtifactsTest.php b/tests/Api/Packages/ArtifactsTest.php index 1d2d56c..101c245 100644 --- a/tests/Api/Packages/ArtifactsTest.php +++ b/tests/Api/Packages/ArtifactsTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Packages; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class ArtifactsTest extends ApiTestCase @@ -88,7 +89,7 @@ public function testShowPackageArtifacts() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/packages/acme-website/package/artifacts/')) + ->with($this->equalTo('/packages/acme-website/package/artifacts/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->showPackageArtifacts('acme-website/package')); diff --git a/tests/Api/PackagesTest.php b/tests/Api/PackagesTest.php index a79df6a..766d6ac 100644 --- a/tests/Api/PackagesTest.php +++ b/tests/Api/PackagesTest.php @@ -46,11 +46,16 @@ public function testAllWithFilters() 'origin' => Packages::ORIGIN_PRIVATE, ]; + $expectedQueryParams = [ + 'origin' => Packages::ORIGIN_PRIVATE, + 'limit' => AbstractApi::DEFAULT_LIMIT, + ]; + /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/packages/'), $this->equalTo($filters)) + ->with($this->equalTo('/packages/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); $this->assertSame($expected, $api->all($filters)); @@ -273,7 +278,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/packages/composer/composer/customers/')) + ->with($this->equalTo('/packages/composer/composer/customers/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listCustomers('composer/composer')); @@ -293,7 +298,7 @@ public function testListDependents() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/packages/acme-website/core-package/dependents/')) + ->with($this->equalTo('/packages/acme-website/core-package/dependents/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listDependents($packageName)); @@ -322,7 +327,7 @@ public function testListSecurityIssues() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/packages/acme-website/core-package/security-issues/')) + ->with($this->equalTo('/packages/acme-website/core-package/security-issues/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listSecurityIssues($packageName)); diff --git a/tests/Api/Projects/MirroredRepositoriesTest.php b/tests/Api/Projects/MirroredRepositoriesTest.php index 1915e89..8f5c79a 100644 --- a/tests/Api/Projects/MirroredRepositoriesTest.php +++ b/tests/Api/Projects/MirroredRepositoriesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Projects; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class MirroredRepositoriesTest extends ApiTestCase @@ -25,7 +26,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/mirrored-repositories/')) + ->with($this->equalTo('/subrepositories/project/mirrored-repositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($projectName)); @@ -108,7 +109,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/mirrored-repositories/1/packages/')) + ->with($this->equalTo('/subrepositories/project/mirrored-repositories/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages($projectName, 1)); diff --git a/tests/Api/Projects/PackagesTest.php b/tests/Api/Projects/PackagesTest.php index 4730d6d..d4ea43d 100644 --- a/tests/Api/Projects/PackagesTest.php +++ b/tests/Api/Projects/PackagesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Projects; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; use PrivatePackagist\ApiClient\Exception\InvalidArgumentException; @@ -29,7 +30,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/packages/')) + ->with($this->equalTo('/subrepositories/project/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($projectName)); @@ -49,11 +50,16 @@ public function testAllWithFilters() 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, ]; + $expectedQueryParams = [ + 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, + 'limit' => AbstractApi::DEFAULT_LIMIT, + ]; + /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/packages/'), $this->equalTo($filters)) + ->with($this->equalTo('/subrepositories/project/packages/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); $this->assertSame($expected, $api->all($projectName, $filters)); diff --git a/tests/Api/ProjectsTest.php b/tests/Api/ProjectsTest.php index 24714d7..b22b67d 100644 --- a/tests/Api/ProjectsTest.php +++ b/tests/Api/ProjectsTest.php @@ -23,7 +23,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/')) + ->with($this->equalTo('/subrepositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -89,7 +89,7 @@ public function testListTeams() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/teams/')) + ->with($this->equalTo('/subrepositories/project/teams/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTeams('project')); @@ -150,7 +150,7 @@ public function testListTokens() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/project/tokens/')) + ->with($this->equalTo('/subrepositories/project/tokens/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTokens('project')); diff --git a/tests/Api/SecurityIssuesTest.php b/tests/Api/SecurityIssuesTest.php index f46b6fc..3d0f384 100644 --- a/tests/Api/SecurityIssuesTest.php +++ b/tests/Api/SecurityIssuesTest.php @@ -63,11 +63,16 @@ public function testAllWithFilters(): void 'security-issue-state' => SecurityIssues::STATE_OPEN, ]; + $expectedQueryParams = [ + 'security-issue-state' => SecurityIssues::STATE_OPEN, + 'limit' => AbstractApi::DEFAULT_LIMIT, + ]; + /** @var SecurityIssues&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/security-issues/'), $this->equalTo($filters)) + ->with($this->equalTo('/security-issues/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); $this->assertSame($expected, $api->all($filters)); diff --git a/tests/Api/Suborganizations/MirroredRepositoriesTest.php b/tests/Api/Suborganizations/MirroredRepositoriesTest.php index 1421d44..f83c0f2 100644 --- a/tests/Api/Suborganizations/MirroredRepositoriesTest.php +++ b/tests/Api/Suborganizations/MirroredRepositoriesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Suborganizations; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class MirroredRepositoriesTest extends ApiTestCase @@ -25,7 +26,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/mirrored-repositories/')) + ->with($this->equalTo('/suborganizations/suborganization/mirrored-repositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($suborganizationName)); @@ -108,7 +109,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/mirrored-repositories/1/packages/')) + ->with($this->equalTo('/suborganizations/suborganization/mirrored-repositories/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages($suborganizationName, 1)); diff --git a/tests/Api/Suborganizations/PackagesTest.php b/tests/Api/Suborganizations/PackagesTest.php index 06595a3..33e3c6f 100644 --- a/tests/Api/Suborganizations/PackagesTest.php +++ b/tests/Api/Suborganizations/PackagesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Suborganizations; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; use PrivatePackagist\ApiClient\Exception\InvalidArgumentException; @@ -29,7 +30,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/packages/')) + ->with($this->equalTo('/suborganizations/suborganization/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($suborganizationName)); @@ -49,11 +50,16 @@ public function testAllWithFilters() 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, ]; + $expectedQueryParams = [ + 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, + 'limit' => AbstractApi::DEFAULT_LIMIT, + ]; + /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/packages/'), $this->equalTo($filters)) + ->with($this->equalTo('/suborganizations/suborganization/packages/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); $this->assertSame($expected, $api->all($suborganizationName, $filters)); @@ -207,7 +213,7 @@ public function testListDependents() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/packages/acme-website/core-package/dependents/')) + ->with($this->equalTo('/suborganizations/suborganization/packages/acme-website/core-package/dependents/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listDependents($suborganizationName, $packageName)); diff --git a/tests/Api/SuborganizationsTest.php b/tests/Api/SuborganizationsTest.php index 9b28bda..21f75b9 100644 --- a/tests/Api/SuborganizationsTest.php +++ b/tests/Api/SuborganizationsTest.php @@ -23,7 +23,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/')) + ->with($this->equalTo('/suborganizations/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -89,7 +89,7 @@ public function testListTeams() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/teams/')) + ->with($this->equalTo('/suborganizations/suborganization/teams/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTeams('suborganization')); @@ -150,7 +150,7 @@ public function testListTokens() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/suborganizations/suborganization/tokens/')) + ->with($this->equalTo('/suborganizations/suborganization/tokens/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTokens('suborganization')); diff --git a/tests/Api/Subrepositories/MirroredRepositoriesTest.php b/tests/Api/Subrepositories/MirroredRepositoriesTest.php index dd85be3..516ef9f 100644 --- a/tests/Api/Subrepositories/MirroredRepositoriesTest.php +++ b/tests/Api/Subrepositories/MirroredRepositoriesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Subrepositories; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; class MirroredRepositoriesTest extends ApiTestCase @@ -25,7 +26,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/mirrored-repositories/')) + ->with($this->equalTo('/subrepositories/subrepository/mirrored-repositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($subrepositoryName)); @@ -108,7 +109,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/mirrored-repositories/1/packages/')) + ->with($this->equalTo('/subrepositories/subrepository/mirrored-repositories/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages($subrepositoryName, 1)); diff --git a/tests/Api/Subrepositories/PackagesTest.php b/tests/Api/Subrepositories/PackagesTest.php index e1ca277..c21a8aa 100644 --- a/tests/Api/Subrepositories/PackagesTest.php +++ b/tests/Api/Subrepositories/PackagesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\Subrepositories; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; use PrivatePackagist\ApiClient\Exception\InvalidArgumentException; @@ -29,7 +30,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/packages/')) + ->with($this->equalTo('/subrepositories/subrepository/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all($subrepositoryName)); @@ -49,11 +50,16 @@ public function testAllWithFilters() 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, ]; + $expectedQueryParams = [ + 'origin' => \PrivatePackagist\ApiClient\Api\Packages::ORIGIN_PRIVATE, + 'limit' => AbstractApi::DEFAULT_LIMIT, + ]; + /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/packages/'), $this->equalTo($filters)) + ->with($this->equalTo('/subrepositories/subrepository/packages/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); $this->assertSame($expected, $api->all($subrepositoryName, $filters)); @@ -207,7 +213,7 @@ public function testListDependents() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/packages/acme-website/core-package/dependents/')) + ->with($this->equalTo('/subrepositories/subrepository/packages/acme-website/core-package/dependents/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listDependents($subrepositoryName, $packageName)); diff --git a/tests/Api/SubrepositoriesTest.php b/tests/Api/SubrepositoriesTest.php index 54622a1..7fd389a 100644 --- a/tests/Api/SubrepositoriesTest.php +++ b/tests/Api/SubrepositoriesTest.php @@ -23,7 +23,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/')) + ->with($this->equalTo('/subrepositories/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -89,7 +89,7 @@ public function testListTeams() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/teams/')) + ->with($this->equalTo('/subrepositories/subrepository/teams/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTeams('subrepository')); @@ -150,7 +150,7 @@ public function testListTokens() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/subrepositories/subrepository/tokens/')) + ->with($this->equalTo('/subrepositories/subrepository/tokens/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listTokens('subrepository')); diff --git a/tests/Api/SynchronizationsTest.php b/tests/Api/SynchronizationsTest.php index dc9d3d0..95deb56 100644 --- a/tests/Api/SynchronizationsTest.php +++ b/tests/Api/SynchronizationsTest.php @@ -33,7 +33,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/synchronizations/')) + ->with($this->equalTo('/synchronizations/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); diff --git a/tests/Api/TeamsTest.php b/tests/Api/TeamsTest.php index 4d2a143..f2328a2 100644 --- a/tests/Api/TeamsTest.php +++ b/tests/Api/TeamsTest.php @@ -36,7 +36,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/teams/')) + ->with($this->equalTo('/teams/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); @@ -55,7 +55,7 @@ public function testPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/teams/1/packages/')) + ->with($this->equalTo('/teams/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->packages(1)); @@ -151,9 +151,6 @@ public function testShowTeam(): void ->with($this->equalTo('/teams/123/')) ->willReturn($expected); - $permissions = new TeamPermissions; - $permissions->canEditTeamPackages = true; - $permissions->canViewVendorCustomers = true; $this->assertSame($expected, $api->show(123)); } diff --git a/tests/Api/TokensTest.php b/tests/Api/TokensTest.php index c1f6d77..29b56eb 100644 --- a/tests/Api/TokensTest.php +++ b/tests/Api/TokensTest.php @@ -31,7 +31,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->equalTo('/tokens/')) + ->with($this->equalTo('/tokens/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); diff --git a/tests/Api/VendorBundles/PackagesTest.php b/tests/Api/VendorBundles/PackagesTest.php index 920a39e..22c4b46 100644 --- a/tests/Api/VendorBundles/PackagesTest.php +++ b/tests/Api/VendorBundles/PackagesTest.php @@ -10,6 +10,7 @@ namespace PrivatePackagist\ApiClient\Api\VendorBundles; use PHPUnit\Framework\MockObject\MockObject; +use PrivatePackagist\ApiClient\Api\AbstractApi; use PrivatePackagist\ApiClient\Api\ApiTestCase; use PrivatePackagist\ApiClient\Exception\InvalidArgumentException; @@ -29,7 +30,7 @@ public function testListPackages() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->identicalTo('/vendor-bundles/1/packages/')) + ->with($this->identicalTo('/vendor-bundles/1/packages/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->listPackages(1)); diff --git a/tests/Api/VendorBundlesTest.php b/tests/Api/VendorBundlesTest.php index 0ee7347..4d45c93 100644 --- a/tests/Api/VendorBundlesTest.php +++ b/tests/Api/VendorBundlesTest.php @@ -30,7 +30,7 @@ public function testAll() $api = $this->getApiMock(); $api->expects($this->once()) ->method('get') - ->with($this->identicalTo('/vendor-bundles/')) + ->with($this->identicalTo('/vendor-bundles/'), $this->identicalTo(['limit' => AbstractApi::DEFAULT_LIMIT])) ->willReturn($expected); $this->assertSame($expected, $api->all()); diff --git a/tests/HttpClient/Plugin/AutoPaginatorTest.php b/tests/HttpClient/Plugin/AutoPaginatorTest.php new file mode 100644 index 0000000..f2c1d8c --- /dev/null +++ b/tests/HttpClient/Plugin/AutoPaginatorTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PrivatePackagist\ApiClient\HttpClient\Plugin; + +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Psr7\Response; +use Http\Discovery\Psr17FactoryDiscovery; +use Http\Promise\FulfilledPromise; +use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; +use Psr\Http\Message\RequestInterface; + +class AutoPaginatorTest extends PluginTestCase +{ + /** @var AutoPaginator */ + private $plugin; + + protected function setUp(): void + { + parent::setUp(); + + $this->plugin = new AutoPaginator( + Psr17FactoryDiscovery::findRequestFactory(), + Psr17FactoryDiscovery::findStreamFactory(), + new ResponseMediator() + ); + } + + public function testPaginate(): void + { + $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); + + $innerResponse = new Response( + 200, + [ + 'Link' => '; rel="first", ; rel="next", ; rel="last"', + 'Content-Type' => 'application/json', + + ], + (string) json_encode([1]) + ); + + $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { + return new FulfilledPromise($innerResponse); + }, function (RequestInterface $request) { + $this->assertSame('https://packagist.com/api/packages?page=2', (string) $request->getUri()); + return new FulfilledPromise(new Response(200, ['Content-Type' => 'application/json'], (string) json_encode([2]))); + })->wait(); + + $this->assertNotSame($innerResponse, $response); + $this->assertSame(json_encode([1, 2]), (string) $response->getBody()->getContents()); + } + + public function testNoLinkHeader(): void + { + $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); + + $innerResponse = new Response(); + + $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { + return new FulfilledPromise($innerResponse); + }, $this->first)->wait(); + + $this->assertSame($innerResponse, $response); + } + + public function testNoNextLinkHeader(): void + { + $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); + + $innerResponse = new Response(200, ['Link' => '; rel="first", ; rel="last"']); + + $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { + return new FulfilledPromise($innerResponse); + }, $this->first)->wait(); + + $this->assertSame($innerResponse, $response); + } +} diff --git a/tests/HttpClient/Plugin/TrustedPublishingTokenExchangeTest.php b/tests/HttpClient/Plugin/TrustedPublishingTokenExchangeTest.php index da3c0ed..3dcb540 100644 --- a/tests/HttpClient/Plugin/TrustedPublishingTokenExchangeTest.php +++ b/tests/HttpClient/Plugin/TrustedPublishingTokenExchangeTest.php @@ -17,6 +17,7 @@ use PrivatePackagist\ApiClient\HttpClient\HttpPluginClientBuilder; use PrivatePackagist\OIDC\Identities\Token; use PrivatePackagist\OIDC\Identities\TokenGeneratorInterface; +use Psr\Http\Message\RequestInterface; class TrustedPublishingTokenExchangeTest extends PluginTestCase { @@ -52,7 +53,7 @@ public function testTokenExchange(): void $this->httpClient->addResponse(new Response(200, [], json_encode(['audience' => 'test']))); $this->httpClient->addResponse(new Response(200, [], json_encode(['key' => 'key', 'secret' => 'secret']))); - $this->plugin->handleRequest($request, function (Request $request) use (&$requestAfterPlugin) { + $this->plugin->handleRequest($request, function (RequestInterface $request) use (&$requestAfterPlugin) { $requestAfterPlugin = $request; return new FulfilledPromise($request); From 9d29b666811f9638a441d34f5a6f3df9b79fde8e Mon Sep 17 00:00:00 2001 From: Steven Rombauts Date: Wed, 26 Nov 2025 12:06:15 +0100 Subject: [PATCH 2/3] Add `getCollection()` method and handle auto-pagination there --- src/Api/AbstractApi.php | 61 ++++++++++++++++++++ src/Api/Packages.php | 2 +- tests/Api/AbstractApiTest.php | 103 ++++++++++++++++++++++++++++++++++ tests/Api/ApiTestCase.php | 2 +- tests/Api/PackagesTest.php | 4 +- 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 tests/Api/AbstractApiTest.php diff --git a/src/Api/AbstractApi.php b/src/Api/AbstractApi.php index f1dc9da..2e33c21 100644 --- a/src/Api/AbstractApi.php +++ b/src/Api/AbstractApi.php @@ -9,6 +9,7 @@ namespace PrivatePackagist\ApiClient\Api; +use Composer\Pcre\Preg; use PrivatePackagist\ApiClient\Client; use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; @@ -49,6 +50,47 @@ protected function get($path, array $parameters = [], array $headers = []) return $this->responseMediator->getContent($response); } + /** + * @param string $path + * @param array $parameters + * @param array $headers + * @return array + */ + protected function getCollection($path, array $parameters = [], array $headers = []) + { + if (count($parameters) > 0) { + $path .= '?'.http_build_query($parameters); + } + + $content = []; + $nextUrl = $path; + + do { + $response = $this->client->getHttpClient()->get( + $nextUrl, + array_merge($headers, [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ]) + ); + + $pageContent = $this->responseMediator->getContent($response); + + if ($response->getStatusCode() !== 200) { + return $pageContent; + } + + $content = array_merge($content, $pageContent); + + $nextUrl = null; + if ($response->hasHeader('Link')) { + $nextUrl = $this->parseLinkHeader($response->getHeaderLine('Link'), 'next'); + } + } while ($nextUrl !== null); + + return $content; + } + /** * @param string $path * @param array $parameters @@ -129,4 +171,23 @@ protected function createJsonBody(array $parameters) { return (count($parameters) === 0) ? null : json_encode($parameters); } + + /** + * @param string $header + * @param string $type + * @return string|null + */ + private function parseLinkHeader(string $header, string $type): ?string + { + foreach (explode(',', $header) as $relation) { + if (Preg::isMatch('/<(.*)>; rel="(.*)"/i', \trim($relation, ','), $match)) { + /** @var string[] $match */ + if (3 === count($match) && $match[2] === $type) { + return $match[1]; + } + } + } + + return null; + } } diff --git a/src/Api/Packages.php b/src/Api/Packages.php index a1bc543..1b1f4b2 100644 --- a/src/Api/Packages.php +++ b/src/Api/Packages.php @@ -54,7 +54,7 @@ public function all(array $filters = []) $filters = array_merge(['limit' => self::DEFAULT_LIMIT], $filters); - return $this->get('/packages/', $filters); + return $this->getCollection('/packages/', $filters); } public function show($packageIdOrName) diff --git a/tests/Api/AbstractApiTest.php b/tests/Api/AbstractApiTest.php new file mode 100644 index 0000000..0949f0f --- /dev/null +++ b/tests/Api/AbstractApiTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PrivatePackagist\ApiClient\Api; + +use GuzzleHttp\Psr7\Response; +use Http\Client\HttpClient; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use PrivatePackagist\ApiClient\Client; +use PrivatePackagist\ApiClient\HttpClient\HttpPluginClientBuilder; +use Psr\Http\Message\RequestInterface; + +class AbstractApiTest extends TestCase +{ + /** + * @var TestableAbstractApi + */ + private $api; + + /** + * @var HttpClient&MockObject + */ + private $httpClient; + + protected function setUp(): void + { + parent::setUp(); + + $this->httpClient = $this->getMockBuilder(HttpClient::class) + ->setMethods(['sendRequest']) + ->getMock(); + + $client = new Client(new HttpPluginClientBuilder($this->httpClient)); + $this->api = new TestableAbstractApi($client); + } + + public function testGetCollectionWithPagination() + { + $page1 = [ + ['id' => 1, 'name' => 'acme/package1'], + ]; + + $page2 = [ + ['id' => 2, 'name' => 'acme/package2'], + ]; + + $response1 = new Response( + 200, + [ + 'Content-Type' => 'application/json', + 'Link' => '; rel="first", ; rel="next", ; rel="last"', + ], + json_encode($page1) + ); + + $response2 = new Response( + 200, + ['Content-Type' => 'application/json'], + json_encode($page2) + ); + + $matcher = $this->exactly(2); + $this->httpClient + ->expects($matcher) + ->method('sendRequest') + ->willReturnCallback(function (RequestInterface $request) use ($matcher, $response1, $response2) { + $uri = (string) $request->getUri(); + + switch ($matcher->getInvocationCount()) { + case 1: + $this->assertSame('https://packagist.com/api/packages/?limit=500', $uri); + return $response1; + case 2: + $this->assertSame('https://packagist.com/api/packages?page=2&limit=500', $uri); + return $response2; + } + + $this->fail('Unexpected request to: ' . $uri); + }); + + $result = $this->api->testGetCollection('/packages/', ['limit' => AbstractApi::DEFAULT_LIMIT]); + + $this->assertSame(array_merge($page1, $page2), $result); + } +} + +/** + * Testable concrete implementation of AbstractApi for testing purposes + */ +class TestableAbstractApi extends AbstractApi +{ + public function testGetCollection($path, array $parameters = [], array $headers = []) + { + return $this->getCollection($path, $parameters, $headers); + } +} \ No newline at end of file diff --git a/tests/Api/ApiTestCase.php b/tests/Api/ApiTestCase.php index b0246b9..8caad60 100644 --- a/tests/Api/ApiTestCase.php +++ b/tests/Api/ApiTestCase.php @@ -39,7 +39,7 @@ protected function getApiMock() $client = new Client(new HttpPluginClientBuilder($httpClient)); return $this->getMockBuilder($this->getApiClass()) - ->setMethods(['get', 'post', 'postFile', 'patch', 'delete', 'put', 'head']) + ->setMethods(['get', 'getCollection', 'post', 'postFile', 'patch', 'delete', 'put', 'head']) ->setConstructorArgs([$client]) ->getMock(); } diff --git a/tests/Api/PackagesTest.php b/tests/Api/PackagesTest.php index 766d6ac..9710eef 100644 --- a/tests/Api/PackagesTest.php +++ b/tests/Api/PackagesTest.php @@ -26,7 +26,7 @@ public function testAll() /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) - ->method('get') + ->method('getCollection') ->with($this->equalTo('/packages/')) ->willReturn($expected); @@ -54,7 +54,7 @@ public function testAllWithFilters() /** @var Packages&MockObject $api */ $api = $this->getApiMock(); $api->expects($this->once()) - ->method('get') + ->method('getCollection') ->with($this->equalTo('/packages/'), $this->equalTo($expectedQueryParams)) ->willReturn($expected); From 73705ceb6a5442a746179ad3a02671c4a4c95986 Mon Sep 17 00:00:00 2001 From: Steven Rombauts Date: Wed, 26 Nov 2025 12:07:31 +0100 Subject: [PATCH 3/3] Remove the `AutoPaginator` plugin --- src/Client.php | 2 - src/HttpClient/Plugin/AutoPaginator.php | 85 ------------------- tests/HttpClient/Plugin/AutoPaginatorTest.php | 85 ------------------- 3 files changed, 172 deletions(-) delete mode 100644 src/HttpClient/Plugin/AutoPaginator.php delete mode 100644 tests/HttpClient/Plugin/AutoPaginatorTest.php diff --git a/src/Client.php b/src/Client.php index 79c0ebf..5d532d1 100644 --- a/src/Client.php +++ b/src/Client.php @@ -13,7 +13,6 @@ use Http\Discovery\Psr17FactoryDiscovery; use PrivatePackagist\ApiClient\HttpClient\HttpPluginClientBuilder; use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; -use PrivatePackagist\ApiClient\HttpClient\Plugin\AutoPaginator; use PrivatePackagist\ApiClient\HttpClient\Plugin\ExceptionThrower; use PrivatePackagist\ApiClient\HttpClient\Plugin\PathPrepend; use PrivatePackagist\ApiClient\HttpClient\Plugin\RequestSignature; @@ -50,7 +49,6 @@ public function __construct(?HttpPluginClientBuilder $httpClientBuilder = null, } $builder->addPlugin(new Plugin\HeaderDefaultsPlugin($headers)); $builder->addPlugin(new ExceptionThrower($this->responseMediator)); - $builder->addPlugin(new AutoPaginator($httpClientBuilder->getRequestFactory(), $httpClientBuilder->getStreamFactory(), $this->responseMediator)); } /** diff --git a/src/HttpClient/Plugin/AutoPaginator.php b/src/HttpClient/Plugin/AutoPaginator.php deleted file mode 100644 index e72c7ba..0000000 --- a/src/HttpClient/Plugin/AutoPaginator.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace PrivatePackagist\ApiClient\HttpClient\Plugin; - -use Composer\Pcre\Preg; -use Http\Client\Common\Plugin; -use Http\Message\RequestFactory; -use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; -use Psr\Http\Message\RequestFactoryInterface; -use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamFactoryInterface; - -class AutoPaginator implements Plugin -{ - use Plugin\VersionBridgePlugin; - - /** @var RequestFactory|RequestFactoryInterface */ - private $requestFactory; - /** @var StreamFactoryInterface */ - private $streamFactory; - /** @var ResponseMediator */ - private $responseMediator; - - /** - * @param RequestFactory|RequestFactoryInterface $requestFactory - */ - public function __construct( - $requestFactory, - StreamFactoryInterface $streamFactory, - ResponseMediator $responseMediator - ) { - $this->requestFactory = $requestFactory; - $this->streamFactory = $streamFactory; - $this->responseMediator = $responseMediator; - } - - protected function doHandleRequest(RequestInterface $request, callable $next, callable $first) - { - return $next($request)->then(function (ResponseInterface $response) use ($first) { - if (!$response->hasHeader('Link')) { - return $response; - } - - $next = $this->parseLinkHeader($response->getHeaderLine('Link'), 'next'); - if (!$next) { - return $response; - } - - $nextResponse = $first($this->requestFactory->createRequest('GET', $next))->wait(); - - if ($nextResponse->getStatusCode() !== 200) { - return $nextResponse; - } - - return $response - ->withoutHeader('Link') - ->withBody($this->streamFactory->createStream(json_encode(array_merge( - $this->responseMediator->getContent($response), - $this->responseMediator->getContent($nextResponse) - )))); - }); - } - - private function parseLinkHeader(string $header, string $type): ?string - { - foreach (explode(',', $header) as $relation) { - if (Preg::isMatch('/<(.*)>; rel="(.*)"/i', \trim($relation, ','), $match)) { - /** @var string[] $match */ - if (3 === count($match) && $match[2] === $type) { - return $match[1]; - } - } - } - - return null; - } -} diff --git a/tests/HttpClient/Plugin/AutoPaginatorTest.php b/tests/HttpClient/Plugin/AutoPaginatorTest.php deleted file mode 100644 index f2c1d8c..0000000 --- a/tests/HttpClient/Plugin/AutoPaginatorTest.php +++ /dev/null @@ -1,85 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace PrivatePackagist\ApiClient\HttpClient\Plugin; - -use GuzzleHttp\Psr7\Request; -use GuzzleHttp\Psr7\Response; -use Http\Discovery\Psr17FactoryDiscovery; -use Http\Promise\FulfilledPromise; -use PrivatePackagist\ApiClient\HttpClient\Message\ResponseMediator; -use Psr\Http\Message\RequestInterface; - -class AutoPaginatorTest extends PluginTestCase -{ - /** @var AutoPaginator */ - private $plugin; - - protected function setUp(): void - { - parent::setUp(); - - $this->plugin = new AutoPaginator( - Psr17FactoryDiscovery::findRequestFactory(), - Psr17FactoryDiscovery::findStreamFactory(), - new ResponseMediator() - ); - } - - public function testPaginate(): void - { - $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); - - $innerResponse = new Response( - 200, - [ - 'Link' => '; rel="first", ; rel="next", ; rel="last"', - 'Content-Type' => 'application/json', - - ], - (string) json_encode([1]) - ); - - $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { - return new FulfilledPromise($innerResponse); - }, function (RequestInterface $request) { - $this->assertSame('https://packagist.com/api/packages?page=2', (string) $request->getUri()); - return new FulfilledPromise(new Response(200, ['Content-Type' => 'application/json'], (string) json_encode([2]))); - })->wait(); - - $this->assertNotSame($innerResponse, $response); - $this->assertSame(json_encode([1, 2]), (string) $response->getBody()->getContents()); - } - - public function testNoLinkHeader(): void - { - $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); - - $innerResponse = new Response(); - - $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { - return new FulfilledPromise($innerResponse); - }, $this->first)->wait(); - - $this->assertSame($innerResponse, $response); - } - - public function testNoNextLinkHeader(): void - { - $request = new Request('POST', '/packages/', [], json_encode(['foo' => 'bar'])); - - $innerResponse = new Response(200, ['Link' => '; rel="first", ; rel="last"']); - - $response = $this->plugin->handleRequest($request, function () use ($innerResponse) { - return new FulfilledPromise($innerResponse); - }, $this->first)->wait(); - - $this->assertSame($innerResponse, $response); - } -}