From e49bc7313ae53e26501993353dcc1d404ae26b01 Mon Sep 17 00:00:00 2001 From: Fabien Bourigault Date: Fri, 2 Jun 2017 17:26:20 +0200 Subject: [PATCH] replace buzz by httplug --- README.md | 31 +-- UPGRADE.md | 14 ++ composer.json | 11 +- doc/customize.md | 28 +++ lib/Gitlab/Api/AbstractApi.php | 99 +++++++- lib/Gitlab/Client.php | 188 ++++++-------- lib/Gitlab/HttpClient/Builder.php | 117 +++++++++ lib/Gitlab/HttpClient/HttpClient.php | 231 ------------------ lib/Gitlab/HttpClient/HttpClientInterface.php | 98 -------- .../HttpClient/Listener/AuthListener.php | 95 ------- .../HttpClient/Listener/ErrorListener.php | 97 -------- .../Listener/PaginationListener.php | 38 --- lib/Gitlab/HttpClient/Message/FormRequest.php | 8 - lib/Gitlab/HttpClient/Message/Request.php | 8 - lib/Gitlab/HttpClient/Message/Response.php | 48 ---- .../HttpClient/Message/ResponseMediator.php | 73 ++++++ lib/Gitlab/HttpClient/Plugin/ApiVersion.php | 29 +++ .../HttpClient/Plugin/Authentication.php | 86 +++++++ .../Plugin/GitlabExceptionThrower.php | 86 +++++++ lib/Gitlab/HttpClient/Plugin/History.php | 44 ++++ lib/Gitlab/ResultPager.php | 27 +- test/Gitlab/Tests/Api/AbstractApiTest.php | 177 -------------- test/Gitlab/Tests/Api/ApiTestCase.php | 23 -- test/Gitlab/Tests/Api/DeployKeysTest.php | 2 +- test/Gitlab/Tests/Api/GroupsTest.php | 2 +- test/Gitlab/Tests/Api/IssuesTest.php | 2 +- test/Gitlab/Tests/Api/KeysTest.php | 4 +- test/Gitlab/Tests/Api/MergeRequestsTest.php | 2 +- test/Gitlab/Tests/Api/MilestonesTest.php | 2 +- .../Tests/Api/ProjectNamespacesTest.php | 2 +- test/Gitlab/Tests/Api/ProjectsTest.php | 2 +- test/Gitlab/Tests/Api/RepositoriesTest.php | 2 +- test/Gitlab/Tests/Api/SnippetsTest.php | 2 +- test/Gitlab/Tests/Api/SystemHooksTest.php | 2 +- test/Gitlab/Tests/Api/TagsTest.php | 4 +- test/Gitlab/Tests/Api/TestCase.php | 37 ++- test/Gitlab/Tests/Api/UsersTest.php | 2 +- test/Gitlab/Tests/HttpClient/BuilderTest.php | 54 ++++ .../Message/ResponseMediatorTest.php | 77 ++++++ .../HttpClient/Plugin/ApiVersionTest.php | 67 +++++ test/Gitlab/Tests/ResultPagerTest.php | 113 +++++++++ 41 files changed, 1012 insertions(+), 1022 deletions(-) create mode 100644 UPGRADE.md create mode 100644 doc/customize.md create mode 100644 lib/Gitlab/HttpClient/Builder.php delete mode 100644 lib/Gitlab/HttpClient/HttpClient.php delete mode 100644 lib/Gitlab/HttpClient/HttpClientInterface.php delete mode 100644 lib/Gitlab/HttpClient/Listener/AuthListener.php delete mode 100644 lib/Gitlab/HttpClient/Listener/ErrorListener.php delete mode 100644 lib/Gitlab/HttpClient/Listener/PaginationListener.php delete mode 100644 lib/Gitlab/HttpClient/Message/FormRequest.php delete mode 100644 lib/Gitlab/HttpClient/Message/Request.php delete mode 100644 lib/Gitlab/HttpClient/Message/Response.php create mode 100644 lib/Gitlab/HttpClient/Message/ResponseMediator.php create mode 100644 lib/Gitlab/HttpClient/Plugin/ApiVersion.php create mode 100644 lib/Gitlab/HttpClient/Plugin/Authentication.php create mode 100644 lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php create mode 100644 lib/Gitlab/HttpClient/Plugin/History.php delete mode 100644 test/Gitlab/Tests/Api/AbstractApiTest.php delete mode 100644 test/Gitlab/Tests/Api/ApiTestCase.php create mode 100644 test/Gitlab/Tests/HttpClient/BuilderTest.php create mode 100644 test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php create mode 100644 test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php create mode 100644 test/Gitlab/Tests/ResultPagerTest.php diff --git a/README.md b/README.md index b8cbdb352..a209994c5 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,16 @@ Based on [php-github-api](https://github.com/m4tthumphrey/php-github-api) and co Installation ------------ -1. Install Composer - ```bash - $ curl -sS https://getcomposer.org/installer | php - $ sudo mv composer.phar /usr/local/bin/composer - ``` +Via [composer](https://getcomposer.org) -2. Add the following to your require block in composer.json config. - - > Note: be careful when using the `dev-master` tag as this may have unexpected results depending on your version of - Gitlab. See the Versioning section below for more information. - - `php composer.phar require m4tthumphrey/php-gitlab-api:dev-master` +```bash +composer require m4tthumphrey/php-gitlab-api php-http/guzzle6-adapter +``` -3. Include Composer's autoloader: +Why `php-http/guzzle6-adapter`? We are decoupled from any HTTP messaging client with help by [HTTPlug](http://httplug.io). - ```php - require_once dirname(__DIR__).'/vendor/autoload.php'; - ``` +You can visit [HTTPlug for library users](http://docs.php-http.org/en/latest/httplug/users.html) to get more information about installing HTTPlug related packages. Versioning ---------- @@ -41,8 +32,9 @@ General API Usage ----------------- ```php -$client = new \Gitlab\Client('http://git.yourdomain.com/api/v3/'); // change here -$client->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN); // change here +$client = \Gitlab\Client::create('http://git.yourdomain.com') + ->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN) +; $project = $client->api('projects')->create('My Project', array( 'description' => 'This is a project', @@ -57,8 +49,9 @@ Model Usage You can also use the library in an object oriented manner: ```php -$client = new \Gitlab\Client('http://git.yourdomain.com/api/v3/'); // change here -$client->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN); // change here +$client = \Gitlab\Client::create('http://git.yourdomain.com') + ->authenticate('your_gitlab_token_here', \Gitlab\Client::AUTH_URL_TOKEN) +; # Creating a new project $project = \Gitlab\Model\Project::create($client, 'My Project', array( diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 000000000..b777e0e97 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,14 @@ +# UPGRADE FROM 8.0 to 9.0 + +Since 9.0, lib no longer use buzz 0.7+, instead it has an HTTPlug abstraction layer. + +## `Gitlab\Client` changes + +* The constructor no longer allow to specify base url. Use `setUrl` or `Client::create` instead. +* The default url is set to `https://gitlab.com`. +* The `$options` constructor argument have been removed, the `getOption` and `setOption` methods have been removed. +See [documentation](doc/customize.md) to know how to customize the client timeout and how to use a custom user agent. +* The `setBaseUrl` and `getBaseUrl` methods have been removed. Use `setUrl` instead. +* The `clearHeaders` and `setHeaders` methods have been removed. See [documentation](doc/customize.md) to know how use custom headers. +* The `setHttpClient` method have been removed. Use a `Gitlab\HttpClient\Builder` instead. +* The `getHttpClient` method return type is changed to `Http\Client\Common\HttpMethodsClient`. diff --git a/composer.json b/composer.json index 786bef04f..ffb501948 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,16 @@ ], "require": { "php": "^5.6 || ^7.0", - "ext-curl": "*", - "ext-xml": "*", - "kriswallsmith/buzz": ">=0.7" + "php-http/client-common": "^1.5", + "php-http/client-implementation": "^1.0", + "php-http/discovery": "^1.2", + "php-http/httplug": "^1.1", + "php-http/multipart-stream-builder": "^1.0" }, "require-dev": { + "guzzlehttp/psr7": "^1.2", + "php-http/guzzle6-adapter": "^1.0", + "php-http/mock-client": "^1.0", "phpunit/phpunit": "~4.5" }, "autoload": { diff --git a/doc/customize.md b/doc/customize.md new file mode 100644 index 000000000..6cb130858 --- /dev/null +++ b/doc/customize.md @@ -0,0 +1,28 @@ +## Customize `php-gitlab-api` + +### How to set custom headers (including `User-Agent`)? + +By providing a `Gitlab\HttpClient\Builder` to the `Gitlab\Client` constructor, you can customize the HTTP client. + +```php +$plugin = new Http\Client\Common\Plugin\HeaderSetPlugin([ + 'User-Agent' => 'Foobar', +]); + +$builder = new Gitlab\HttpClient\Builder(); +$builder->addPlugin($plugin); + +$client = new Gitlab\Client($builder); +``` +Read more about [HTTPlug plugins here](http://docs.php-http.org/en/latest/plugins/introduction.html#how-it-works). + +### How to customize the HTTP client timeout? +As timeout configuration is not compatible with HTTP client abstraction, you have to create the `Gitlab\Client` with +an explicit HTTP client implementation. + +```php +$httpClient = Http\Adapter\Guzzle6::createWithConfig([ + 'timeout' => 1.0 +]); +$client = Gitlab\Client::createWithHttpClient($httpClient); +``` diff --git a/lib/Gitlab/Api/AbstractApi.php b/lib/Gitlab/Api/AbstractApi.php index d6e3ab723..e08996973 100644 --- a/lib/Gitlab/Api/AbstractApi.php +++ b/lib/Gitlab/Api/AbstractApi.php @@ -1,6 +1,10 @@ client = $client; + $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); } /** @@ -48,9 +59,11 @@ public function configure() */ protected function get($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->get($path, $parameters, $requestHeaders); + $path = $this->preparePath($path, $parameters); - return $response->getContent(); + $response = $this->client->getHttpClient()->get($path, $requestHeaders); + + return ResponseMediator::getContent($response); } /** @@ -62,9 +75,35 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr */ protected function post($path, array $parameters = array(), $requestHeaders = array(), array $files = array()) { - $response = $this->client->getHttpClient()->post($path, $parameters, $requestHeaders, $files); - - return $response->getContent(); + $path = $this->preparePath($path); + + $body = null; + if (empty($files) && !empty($parameters)) { + $body = $this->streamFactory->createStream(http_build_query($parameters)); + $requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; + } elseif (!empty($files)) { + $builder = new MultipartStreamBuilder($this->streamFactory); + + foreach ($parameters as $name => $value) { + $builder->addResource($name, $value); + } + + foreach ($files as $name => $file) { + $builder->addResource($name, fopen($file, 'r'), [ + 'headers' => [ + 'Content-Type' => $this->guessContentType($file), + ], + 'filename' => basename($file), + ]); + } + + $body = $builder->build(); + $requestHeaders['Content-Type'] = 'multipart/form-data; boundary='.$builder->getBoundary(); + } + + $response = $this->client->getHttpClient()->post($path, $requestHeaders, $body); + + return ResponseMediator::getContent($response); } /** @@ -75,9 +114,13 @@ protected function post($path, array $parameters = array(), $requestHeaders = ar */ protected function patch($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->patch($path, $parameters, $requestHeaders); + $path = $this->preparePath($path); + + $body = empty($parameters) ? null : $this->streamFactory->createStream(http_build_query($parameters)); - return $response->getContent(); + $response = $this->client->getHttpClient()->patch($path, $requestHeaders, $body); + + return ResponseMediator::getContent($response); } /** @@ -88,9 +131,13 @@ protected function patch($path, array $parameters = array(), $requestHeaders = a */ protected function put($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->put($path, $parameters, $requestHeaders); + $path = $this->preparePath($path); + + $body = empty($parameters) ? null : $this->streamFactory->createStream(http_build_query($parameters)); + + $response = $this->client->getHttpClient()->put($path, $requestHeaders, $body); - return $response->getContent(); + return ResponseMediator::getContent($response); } /** @@ -101,9 +148,13 @@ protected function put($path, array $parameters = array(), $requestHeaders = arr */ protected function delete($path, array $parameters = array(), $requestHeaders = array()) { - $response = $this->client->getHttpClient()->delete($path, $parameters, $requestHeaders); + $path = $this->preparePath($path); - return $response->getContent(); + $body = empty($parameters) ? null : $this->streamFactory->createStream(http_build_query($parameters)); + + $response = $this->client->getHttpClient()->delete($path, $requestHeaders, $body); + + return ResponseMediator::getContent($response); } /** @@ -126,4 +177,28 @@ protected function encodePath($path) return str_replace('.', '%2E', $path); } + + private function preparePath($path, array $parameters = []) + { + if (count($parameters) > 0) { + $path .= '?'.http_build_query($parameters); + } + + return $path; + } + + /** + * @param $file + * + * @return string + */ + private function guessContentType($file) + { + if (!class_exists(\finfo::class, false)) { + return 'application/octet-stream'; + } + $finfo = new \finfo(FILEINFO_MIME_TYPE); + + return $finfo->file($file); + } } diff --git a/lib/Gitlab/Client.php b/lib/Gitlab/Client.php index 5f0fd3950..b2cefa372 100644 --- a/lib/Gitlab/Client.php +++ b/lib/Gitlab/Client.php @@ -1,14 +1,18 @@ 'php-gitlab-api (http://github.com/m4tthumphrey/php-gitlab-api)', - 'timeout' => 60 - ); + private $responseHistory; - private $baseUrl; + /** + * @var Builder + */ + private $httpClientBuilder; /** - * The Buzz instance used to communicate with Gitlab + * Instantiate a new Gitlab client * - * @var HttpClient + * @param Builder $httpClientBuilder */ - private $httpClient; + public function __construct(Builder $httpClientBuilder = null) + { + $this->responseHistory = new History(); + $this->httpClientBuilder = $httpClientBuilder ?: new Builder(); + + $this->httpClientBuilder->addPlugin(new GitlabExceptionThrower()); + $this->httpClientBuilder->addPlugin(new HistoryPlugin($this->responseHistory)); + $this->httpClientBuilder->addPlugin(new ApiVersion()); + $this->httpClientBuilder->addPlugin(new HeaderDefaultsPlugin([ + 'User-Agent' => 'php-gitlab-api (http://github.com/m4tthumphrey/php-gitlab-api)', + ])); + + $this->setUrl('https://gitlab.com'); + } /** - * Instantiate a new Gitlab client + * Create a Gitlab\Client using an url. * - * @param string $baseUrl - * @param null|ClientInterface $httpClient Buzz client - * @param array $options + * @param string $url + * + * @return Client */ - public function __construct($baseUrl, ClientInterface $httpClient = null, array $options = array()) + public static function create($url) { - foreach ($options as $name => $value) { - $this->setOption($name, $value); - } + $client = new self(); + $client->setUrl($url); - $httpClient = $httpClient ?: new Curl(); - $httpClient->setTimeout($this->options['timeout']); - $httpClient->setVerifyPeer(false); + return $client; + } - $this->baseUrl = $baseUrl; - $this->httpClient = new HttpClient($this->baseUrl, $this->options, $httpClient); + /** + * Create a Gitlab\Client using an HttpClient. + * + * @param HttpClient $httpClient + * + * @return Client + */ + public static function createWithHttpClient(HttpClient $httpClient) + { + $builder = new Builder($httpClient); - /** - * a Pagination listener on Response - */ - $this->httpClient->addListener( - new PaginationListener() - ); + return new self($builder); } /** @@ -183,117 +201,47 @@ public function api($name) */ public function authenticate($token, $authMethod = self::AUTH_URL_TOKEN, $sudo = null) { - $this->httpClient->addListener( - new AuthListener( - $authMethod, - $token, - $sudo - ) - ); - - return $this; - } - - /** - * @return HttpClient - */ - public function getHttpClient() - { - return $this->httpClient; - } - - /** - * @param HttpClientInterface $httpClient - * @return $this - */ - public function setHttpClient(HttpClientInterface $httpClient) - { - $this->httpClient = $httpClient; + $this->httpClientBuilder->removePlugin(Authentication::class); + $this->httpClientBuilder->addPlugin(new Authentication($authMethod, $token, $sudo)); return $this; } /** * @param string $url - * @return $this - */ - public function setBaseUrl($url) - { - $this->baseUrl = $url; - - return $this; - } - - /** - * @return string - */ - public function getBaseUrl() - { - return $this->baseUrl; - } - - /** - * Clears used headers * * @return $this */ - public function clearHeaders() + public function setUrl($url) { - $this->httpClient->clearHeaders(); + $this->httpClientBuilder->removePlugin(AddHostPlugin::class); + $this->httpClientBuilder->addPlugin(new AddHostPlugin(UriFactoryDiscovery::find()->createUri($url))); return $this; } /** - * @param array $headers - * @return $this - */ - public function setHeaders(array $headers) - { - $this->httpClient->setHeaders($headers); - - return $this; - } - - /** - * @param string $name - * - * @return mixed - * - * @throws InvalidArgumentException + * @param string $api + * @return AbstractApi */ - public function getOption($name) + public function __get($api) { - if (!array_key_exists($name, $this->options)) { - throw new InvalidArgumentException(sprintf('Undefined option called: "%s"', $name)); - } - - return $this->options[$name]; + return $this->api($api); } /** - * @param string $name - * @param mixed $value - * @throws InvalidArgumentException - * @return $this + * @return HttpMethodsClient */ - public function setOption($name, $value) + public function getHttpClient() { - if (!array_key_exists($name, $this->options)) { - throw new InvalidArgumentException(sprintf('Undefined option called: "%s"', $name)); - } - - $this->options[$name] = $value; - - return $this; + return $this->httpClientBuilder->getHttpClient(); } /** - * @param string $api - * @return AbstractApi + * @return History */ - public function __get($api) + public function getResponseHistory() { - return $this->api($api); + return $this->responseHistory; } } diff --git a/lib/Gitlab/HttpClient/Builder.php b/lib/Gitlab/HttpClient/Builder.php new file mode 100644 index 000000000..133091111 --- /dev/null +++ b/lib/Gitlab/HttpClient/Builder.php @@ -0,0 +1,117 @@ + + */ +class Builder +{ + /** + * The object that sends HTTP messages. + * + * @var HttpClient + */ + private $httpClient; + + /** + * A HTTP client with all our plugins. + * + * @var PluginClient + */ + private $pluginClient; + + /** + * @var MessageFactory + */ + private $requestFactory; + + /** + * @var StreamFactory + */ + private $streamFactory; + + /** + * True if we should create a new Plugin client at next request. + * + * @var bool + */ + private $httpClientModified = true; + + /** + * @var Plugin[] + */ + private $plugins = []; + + /** + * @param HttpClient $httpClient + * @param RequestFactory $requestFactory + * @param StreamFactory $streamFactory + */ + public function __construct( + HttpClient $httpClient = null, + RequestFactory $requestFactory = null, + StreamFactory $streamFactory = null + ) { + $this->httpClient = $httpClient ?: HttpClientDiscovery::find(); + $this->requestFactory = $requestFactory ?: MessageFactoryDiscovery::find(); + $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + } + + /** + * @return HttpMethodsClient + */ + public function getHttpClient() + { + if ($this->httpClientModified) { + $this->httpClientModified = false; + + $this->pluginClient = new HttpMethodsClient( + new PluginClient($this->httpClient, $this->plugins), + $this->requestFactory + ); + } + + return $this->pluginClient; + } + + /** + * Add a new plugin to the end of the plugin chain. + * + * @param Plugin $plugin + */ + public function addPlugin(Plugin $plugin) + { + $this->plugins[] = $plugin; + $this->httpClientModified = true; + } + + /** + * Remove a plugin by its fully qualified class name (FQCN). + * + * @param string $fqcn + */ + public function removePlugin($fqcn) + { + foreach ($this->plugins as $idx => $plugin) { + if ($plugin instanceof $fqcn) { + unset($this->plugins[$idx]); + $this->httpClientModified = true; + } + } + } +} diff --git a/lib/Gitlab/HttpClient/HttpClient.php b/lib/Gitlab/HttpClient/HttpClient.php deleted file mode 100644 index 7a84bfb43..000000000 --- a/lib/Gitlab/HttpClient/HttpClient.php +++ /dev/null @@ -1,231 +0,0 @@ - - * @author Matt Humphrey - */ -class HttpClient implements HttpClientInterface -{ - /** - * @var array - */ - protected $options = array( - 'user_agent' => 'php-gitlab-api (http://github.com/m4tthumphrey/php-gitlab-api)', - 'timeout' => 10, - ); - - /** - * @var string - */ - protected $baseUrl; - - /** - * @var ListenerInterface[] - */ - protected $listeners = array(); - /** - * @var array - */ - protected $headers = array(); - - /** - * @var Response - */ - private $lastResponse; - - /** - * @var Request - */ - private $lastRequest; - - /** - * @param string $baseUrl - * @param array $options - * @param ClientInterface $client - */ - public function __construct($baseUrl, array $options, ClientInterface $client) - { - $this->baseUrl = $baseUrl; - $this->options = array_merge($this->options, $options); - $this->client = $client; - - $this->addListener(new ErrorListener($this->options)); - - $this->clearHeaders(); - } - - /** - * {@inheritDoc} - */ - public function setOption($name, $value) - { - $this->options[$name] = $value; - } - - /** - * {@inheritDoc} - */ - public function setHeaders(array $headers) - { - $this->headers = array_merge($this->headers, $headers); - } - - /** - * Clears used headers - */ - public function clearHeaders() - { - $this->headers = array(); - } - - /** - * @param ListenerInterface $listener - */ - public function addListener(ListenerInterface $listener) - { - $this->listeners[get_class($listener)] = $listener; - } - - /** - * {@inheritDoc} - */ - public function get($path, array $parameters = array(), array $headers = array()) - { - if (0 < count($parameters)) { - $path .= (false === strpos($path, '?') ? '?' : '&').http_build_query($parameters, '', '&'); - } - - return $this->request($path, array(), 'GET', $headers); - } - - /** - * {@inheritDoc} - */ - public function post($path, array $parameters = array(), array $headers = array(), array $files = array()) - { - return $this->request($path, $parameters, 'POST', $headers, $files); - } - - /** - * {@inheritDoc} - */ - public function patch($path, array $parameters = array(), array $headers = array()) - { - return $this->request($path, $parameters, 'PATCH', $headers); - } - - /** - * {@inheritDoc} - */ - public function delete($path, array $parameters = array(), array $headers = array()) - { - return $this->request($path, $parameters, 'DELETE', $headers); - } - - /** - * {@inheritDoc} - */ - public function put($path, array $parameters = array(), array $headers = array()) - { - return $this->request($path, $parameters, 'PUT', $headers); - } - - /** - * {@inheritDoc} - */ - public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array(), array $files = array()) - { - $path = trim($this->baseUrl.$path, '/'); - - $request = $this->createRequest($httpMethod, $path, $parameters, $headers, $files); - - $hasListeners = 0 < count($this->listeners); - if ($hasListeners) { - foreach ($this->listeners as $listener) { - $listener->preSend($request); - } - } - - $response = new Response(); - - try { - $this->client->send($request, $response); - } catch (\LogicException $e) { - throw new ErrorException($e->getMessage()); - } catch (\RuntimeException $e) { - throw new RuntimeException($e->getMessage()); - } - - $this->lastRequest = $request; - $this->lastResponse = $response; - - if ($hasListeners) { - foreach ($this->listeners as $listener) { - $listener->postSend($request, $response); - } - } - - return $response; - } - - /** - * @return Request - */ - public function getLastRequest() - { - return $this->lastRequest; - } - - /** - * @return Response - */ - public function getLastResponse() - { - return $this->lastResponse; - } - - /** - * @param string $httpMethod - * @param string $url - * @param array $parameters - * @param array $headers - * @param array $files - * - * @return FormRequest|Request - */ - private function createRequest($httpMethod, $url, array $parameters, array $headers, array $files) - { - if (empty($files)) { - $request = new Request($httpMethod); - $request->setContent(http_build_query($parameters)); - } else { - $request = new FormRequest($httpMethod); - foreach ($parameters as $name => $value) { - $request->setField($name, $value); - } - - foreach ($files as $name => $file) { - $upload = new FormUpload($file); - $request->setField($name, $upload); - } - } - $request->setHeaders($this->headers); - $request->fromUrl($url); - $request->addHeaders($headers); - - return $request; - } -} diff --git a/lib/Gitlab/HttpClient/HttpClientInterface.php b/lib/Gitlab/HttpClient/HttpClientInterface.php deleted file mode 100644 index 674987bec..000000000 --- a/lib/Gitlab/HttpClient/HttpClientInterface.php +++ /dev/null @@ -1,98 +0,0 @@ - - * @author Matt Humphrey - */ -interface HttpClientInterface -{ - /** - * Send a GET request - * - * @param string $path Request path - * @param array $parameters GET Parameters - * @param array $headers Reconfigure the request headers for this call only - * - * @return array Data - */ - public function get($path, array $parameters = array(), array $headers = array()); - - /** - * Send a POST request - * - * @param string $path Request path - * @param array $parameters POST Parameters - * @param array $headers Reconfigure the request headers for this call only - * @param array $files Files paths of files to upload - * - * @return array Data - */ - public function post($path, array $parameters = array(), array $headers = array(), array $files = array()); - - /** - * Send a PATCH request - * - * @param string $path Request path - * @param array $parameters PATCH Parameters - * @param array $headers Reconfigure the request headers for this call only - * - * @return array Data - */ - public function patch($path, array $parameters = array(), array $headers = array()); - - /** - * Send a PUT request - * - * @param string $path Request path - * @param array $parameters PUT Parameters - * @param array $headers Reconfigure the request headers for this call only - * - * @return array Data - */ - public function put($path, array $parameters = array(), array $headers = array()); - - /** - * Send a DELETE request - * - * @param string $path Request path - * @param array $parameters DELETE Parameters - * @param array $headers Reconfigure the request headers for this call only - * - * @return array Data - */ - public function delete($path, array $parameters = array(), array $headers = array()); - - /** - * Send a request to the server, receive a response, - * decode the response and returns an associative array - * - * @param string $path Request API path - * @param array $parameters Parameters - * @param string $httpMethod HTTP method to use - * @param array $headers Request headers - * - * @return array Data - */ - public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array()); - - /** - * Change an option value. - * - * @param string $name The option name - * @param mixed $value The value - * - * @throws InvalidArgumentException - */ - public function setOption($name, $value); - - /** - * Set HTTP headers - * - * @param array $headers - */ - public function setHeaders(array $headers); -} diff --git a/lib/Gitlab/HttpClient/Listener/AuthListener.php b/lib/Gitlab/HttpClient/Listener/AuthListener.php deleted file mode 100644 index 271641122..000000000 --- a/lib/Gitlab/HttpClient/Listener/AuthListener.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @author Matt Humphrey - */ -class AuthListener implements ListenerInterface -{ - /** - * @var string - */ - private $method; - - /** - * @var string - */ - private $token; - - /** - * @var string|null - */ - private $sudo; - - /** - * @param string $method - * @param string $token - * @param string|null $sudo - */ - public function __construct($method, $token, $sudo = null) - { - $this->method = $method; - $this->token = $token; - $this->sudo = $sudo; - } - - /** - * {@inheritDoc} - * - * @throws InvalidArgumentException - */ - public function preSend(RequestInterface $request) - { - // Skip by default - if (null === $this->method) { - return; - } - - switch ($this->method) { - case Client::AUTH_HTTP_TOKEN: - $request->addHeader('PRIVATE-TOKEN: '.$this->token); - if (!is_null($this->sudo)) { - $request->addHeader('SUDO: '.$this->sudo); - } - break; - - case Client::AUTH_URL_TOKEN: - $url = $request->getUrl(); - - $query = array( - 'private_token' => $this->token - ); - - if (!is_null($this->sudo)) { - $query['sudo'] = $this->sudo; - } - - $url .= (false === strpos($url, '?') ? '?' : '&').utf8_encode(http_build_query($query, '', '&')); - - $request->fromUrl(new Url($url)); - break; - - case Client::AUTH_OAUTH_TOKEN: - $request->addHeader('Authorization: Bearer '.$this->token); - if (!is_null($this->sudo)) { - $request->addHeader('SUDO: '.$this->sudo); - } - break; - } - } - - /** - * {@inheritDoc} - */ - public function postSend(RequestInterface $request, MessageInterface $response) - { - } -} diff --git a/lib/Gitlab/HttpClient/Listener/ErrorListener.php b/lib/Gitlab/HttpClient/Listener/ErrorListener.php deleted file mode 100644 index cb2acc68b..000000000 --- a/lib/Gitlab/HttpClient/Listener/ErrorListener.php +++ /dev/null @@ -1,97 +0,0 @@ - - * @author Matt Humphrey - */ -class ErrorListener implements ListenerInterface -{ - /** - * @var array - */ - private $options; - - /** - * @param array $options - */ - public function __construct(array $options) - { - $this->options = $options; - } - - /** - * {@inheritDoc} - */ - public function preSend(RequestInterface $request) - { - } - - /** - * {@inheritDoc} - */ - public function postSend(RequestInterface $request, MessageInterface $response) - { - /** @var $response \Gitlab\HttpClient\Message\Response */ - if ($response->isClientError() || $response->isServerError()) { - $content = $response->getContent(); - if (is_array($content) && isset($content['message'])) { - if (400 == $response->getStatusCode()) { - $message = $this->parseMessage($content['message']); - - throw new ErrorException($message, 400); - } - } - - $errorMessage = null; - if (isset($content['error'])) { - $errorMessage = $content['error']; - if (is_array($content['error'])) { - $errorMessage = implode("\n", $content['error']); - } - } elseif (isset($content['message'])) { - $errorMessage = $this->parseMessage($content['message']); - } else { - $errorMessage = $content; - } - - throw new RuntimeException($errorMessage, $response->getStatusCode()); - } - } - - /** - * @param mixed $message - * @return string - */ - protected function parseMessage($message) - { - $string = $message; - - if (is_array($message)) { - $format = '"%s" %s'; - $errors = array(); - - foreach ($message as $field => $messages) { - if (is_array($messages)) { - $messages = array_unique($messages); - foreach ($messages as $error) { - $errors[] = sprintf($format, $field, $error); - } - } elseif (is_integer($field)) { - $errors[] = $messages; - } else { - $errors[] = sprintf($format, $field, $messages); - } - } - - $string = implode(', ', $errors); - } - - return $string; - } -} diff --git a/lib/Gitlab/HttpClient/Listener/PaginationListener.php b/lib/Gitlab/HttpClient/Listener/PaginationListener.php deleted file mode 100644 index 80ba39ea9..000000000 --- a/lib/Gitlab/HttpClient/Listener/PaginationListener.php +++ /dev/null @@ -1,38 +0,0 @@ -getHeader('Link'); - - if (empty($header)) { - return null; - } - - $pagination = array(); - foreach (explode(',', $header) as $link) { - preg_match('/<(.*)>; rel="(.*)"/i', trim($link, ','), $match); - - if (3 === count($match)) { - $pagination[$match[2]] = $match[1]; - } - } - - $response->setPagination($pagination); - } -} diff --git a/lib/Gitlab/HttpClient/Message/FormRequest.php b/lib/Gitlab/HttpClient/Message/FormRequest.php deleted file mode 100644 index ecd05e17d..000000000 --- a/lib/Gitlab/HttpClient/Message/FormRequest.php +++ /dev/null @@ -1,8 +0,0 @@ -pagination; - } - - /** - * @param array $pagination - */ - public function setPagination(array $pagination) - { - $this->pagination = $pagination; - } - - /** - * {@inheritDoc} - */ - public function getContent() - { - $response = parent::getContent(); - - if ($this->getHeader('Content-Type') === 'application/json') { - $content = json_decode($response, true); - - if (JSON_ERROR_NONE !== json_last_error()) { - return $response; - } - - return $content; - } - - return $response; - } -} diff --git a/lib/Gitlab/HttpClient/Message/ResponseMediator.php b/lib/Gitlab/HttpClient/Message/ResponseMediator.php new file mode 100644 index 000000000..16bd781f0 --- /dev/null +++ b/lib/Gitlab/HttpClient/Message/ResponseMediator.php @@ -0,0 +1,73 @@ +getBody()->__toString(); + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { + $content = json_decode($body, true); + if (JSON_ERROR_NONE === json_last_error()) { + return $content; + } + } + + return $body; + } + + /** + * Extract pagination URIs from Link header. + * + * @param ResponseInterface $response + * + * @return array|null + */ + public static function getPagination(ResponseInterface $response) + { + if (!$response->hasHeader('Link')) { + return null; + } + + $header = self::getHeader($response, 'Link'); + $pagination = array(); + foreach (explode(',', $header) as $link) { + preg_match('/<(.*)>; rel="(.*)"/i', trim($link, ','), $match); + + if (3 === count($match)) { + $pagination[$match[2]] = $match[1]; + } + } + + return $pagination; + } + + + /** + * Get the value for a single header. + * + * @param ResponseInterface $response + * @param string $name + * + * @return string|null + */ + private static function getHeader(ResponseInterface $response, $name) + { + $headers = $response->getHeader($name); + + return array_shift($headers); + } +} diff --git a/lib/Gitlab/HttpClient/Plugin/ApiVersion.php b/lib/Gitlab/HttpClient/Plugin/ApiVersion.php new file mode 100644 index 000000000..3e196ea78 --- /dev/null +++ b/lib/Gitlab/HttpClient/Plugin/ApiVersion.php @@ -0,0 +1,29 @@ + + */ +class ApiVersion implements Plugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $uri = $request->getUri(); + + if (substr($uri->getPath(), 0, 8) !== '/api/v3/') { + $request = $request->withUri($uri->withPath('/api/v3/'.$uri->getPath())); + } + + return $next($request); + } +} diff --git a/lib/Gitlab/HttpClient/Plugin/Authentication.php b/lib/Gitlab/HttpClient/Plugin/Authentication.php new file mode 100644 index 000000000..aac0acc4a --- /dev/null +++ b/lib/Gitlab/HttpClient/Plugin/Authentication.php @@ -0,0 +1,86 @@ + + * @author Fabien Bourigault + */ +class Authentication implements Plugin +{ + /** + * @var string + */ + private $method; + + /** + * @var string + */ + private $token; + + /** + * @var string|null + */ + private $sudo; + + /** + * @param string $method + * @param string $token + * @param string|null $sudo + */ + public function __construct($method, $token, $sudo = null) + { + $this->method = $method; + $this->token = $token; + $this->sudo = $sudo; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + switch ($this->method) { + case Client::AUTH_HTTP_TOKEN: + $request = $request->withHeader('PRIVATE-TOKEN', $this->token); + if (!is_null($this->sudo)) { + $request = $request->withHeader('SUDO', $this->sudo); + } + break; + + case Client::AUTH_URL_TOKEN: + $uri = $request->getUri(); + $query = $uri->getQuery(); + + $parameters = [ + 'private_token' => $this->token, + ]; + + if (!is_null($this->sudo)) { + $parameters['sudo'] = $this->sudo; + } + + $query .= empty($query) ? '' : '&'; + $query .= utf8_encode(http_build_query($parameters, '', '&')); + + $uri = $uri->withQuery($query); + $request = $request->withUri($uri); + break; + + case Client::AUTH_OAUTH_TOKEN: + $request = $request->withHeader('Authorization', 'Bearer '.$this->token); + if (!is_null($this->sudo)) { + $request = $request->withHeader('SUDO', $this->sudo); + } + break; + } + + return $next($request); + } +} diff --git a/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php b/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php new file mode 100644 index 000000000..bb61e3121 --- /dev/null +++ b/lib/Gitlab/HttpClient/Plugin/GitlabExceptionThrower.php @@ -0,0 +1,86 @@ + + * @author Fabien Bourigault + */ +class GitlabExceptionThrower implements Plugin +{ + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + return $next($request)->then(function (ResponseInterface $response) { + if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { + $content = ResponseMediator::getContent($response); + if (is_array($content) && isset($content['message'])) { + if (400 == $response->getStatusCode()) { + $message = $this->parseMessage($content['message']); + + throw new ErrorException($message, 400); + } + } + + $errorMessage = null; + if (isset($content['error'])) { + $errorMessage = $content['error']; + if (is_array($content['error'])) { + $errorMessage = implode("\n", $content['error']); + } + } elseif (isset($content['message'])) { + $errorMessage = $this->parseMessage($content['message']); + } else { + $errorMessage = $content; + } + + throw new RuntimeException($errorMessage, $response->getStatusCode()); + } + + return $response; + }); + } + + /** + * @param mixed $message + * + * @return string + */ + private function parseMessage($message) + { + $string = $message; + + if (is_array($message)) { + $format = '"%s" %s'; + $errors = array(); + + foreach ($message as $field => $messages) { + if (is_array($messages)) { + $messages = array_unique($messages); + foreach ($messages as $error) { + $errors[] = sprintf($format, $field, $error); + } + } elseif (is_integer($field)) { + $errors[] = $messages; + } else { + $errors[] = sprintf($format, $field, $messages); + } + } + + $string = implode(', ', $errors); + } + + return $string; + } +} diff --git a/lib/Gitlab/HttpClient/Plugin/History.php b/lib/Gitlab/HttpClient/Plugin/History.php new file mode 100644 index 000000000..8412356fd --- /dev/null +++ b/lib/Gitlab/HttpClient/Plugin/History.php @@ -0,0 +1,44 @@ + + */ +class History implements Journal +{ + /** + * @var ResponseInterface + */ + private $lastResponse; + + /** + * @return ResponseInterface|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + /** + * {@inheritdoc} + */ + public function addSuccess(RequestInterface $request, ResponseInterface $response) + { + $this->lastResponse = $response; + } + + /** + * {@inheritdoc} + */ + public function addFailure(RequestInterface $request, Exception $exception) + { + } +} diff --git a/lib/Gitlab/ResultPager.php b/lib/Gitlab/ResultPager.php index 3cfb44d13..b436305b1 100644 --- a/lib/Gitlab/ResultPager.php +++ b/lib/Gitlab/ResultPager.php @@ -1,6 +1,7 @@ hasNext()) { $result = array_merge($result, $this->fetchNext()); } @@ -105,7 +103,17 @@ public function fetchLast() */ protected function has($key) { - return !empty($this->client->getHttpClient()->getLastResponse()->getPagination()) && isset($this->client->getHttpClient()->getLastResponse()->getPagination()[$key]); + $lastResponse = $this->client->getResponseHistory()->getLastResponse(); + if ($lastResponse == null) { + return false; + } + + $pagination = ResponseMediator::getPagination($lastResponse); + if ($pagination == null) { + return false; + } + + return isset($pagination[$key]); } /** @@ -113,9 +121,12 @@ protected function has($key) */ protected function get($key) { - if ($this->has($key)) { - $result = $this->client->getHttpClient()->get(strtr($this->client->getHttpClient()->getLastResponse()->getPagination()[$key], array($this->client->getBaseUrl() => '')))->getContent(); - return $result; + if (!$this->has($key)) { + return []; } + + $pagination = ResponseMediator::getPagination($this->client->getResponseHistory()->getLastResponse()); + + return ResponseMediator::getContent($this->client->getHttpClient()->get($pagination[$key])); } } diff --git a/test/Gitlab/Tests/Api/AbstractApiTest.php b/test/Gitlab/Tests/Api/AbstractApiTest.php deleted file mode 100644 index aa1eeaea2..000000000 --- a/test/Gitlab/Tests/Api/AbstractApiTest.php +++ /dev/null @@ -1,177 +0,0 @@ -getResponse('value'); - - $httpClient = $this->getHttpMock(); - $httpClient - ->expects($this->any()) - ->method('get') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($response)); - - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - - $api = $this->getAbstractApiObject($client); - $this->assertEquals('value', $api->get('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); - } - - /** - * @test - */ - public function shouldPassPOSTRequestToClient() - { - $response = $this->getResponse('value'); - - $httpClient = $this->getHttpMock(); - $httpClient - ->expects($this->any()) - ->method('post') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($response)); - - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - - $api = $this->getAbstractApiObject($client); - $this->assertEquals('value', $api->post('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); - } - - /** - * @test - */ - public function shouldPassPUTRequestToClient() - { - $response = $this->getResponse('value'); - - $httpClient = $this->getHttpMock(); - $httpClient - ->expects($this->any()) - ->method('put') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($response)); - - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - - $api = $this->getAbstractApiObject($client); - $this->assertEquals('value', $api->put('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); - } - - /** - * @test - */ - public function shouldPassDELETERequestToClient() - { - $response = $this->getResponse('value'); - - $httpClient = $this->getHttpMock(); - $httpClient - ->expects($this->any()) - ->method('delete') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($response)); - - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - - $api = $this->getAbstractApiObject($client); - $this->assertEquals('value', $api->delete('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); - } - - /** - * @test - */ - public function shouldPassPATCHRequestToClient() - { - $response = $this->getResponse('value'); - - $httpClient = $this->getHttpMock(); - $httpClient - ->expects($this->any()) - ->method('patch') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($response)); - - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - - $api = $this->getAbstractApiObject($client); - $this->assertEquals('value', $api->patch('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); - } - - /** - * @param mixed $value - * @return Response - */ - protected function getResponse($value) - { - $response = new Response(); - $response->setContent($value); - - return $response; - } - - /** - * @param Client $client - * @return AbstractApiTestInstance - */ - protected function getAbstractApiObject(Client $client) - { - return new AbstractApiTestInstance($client); - } -} - -class AbstractApiTestInstance extends AbstractApi -{ - /** - * {@inheritDoc} - */ - public function get($path, array $parameters = array(), $requestHeaders = array()) - { - return parent::get($path, $parameters, $requestHeaders); - } - - /** - * {@inheritDoc} - */ - public function post($path, array $parameters = array(), $requestHeaders = array(), array $files = array()) - { - return parent::post($path, $parameters, $requestHeaders, $files); - } - - /** - * {@inheritDoc} - */ - public function patch($path, array $parameters = array(), $requestHeaders = array()) - { - return parent::patch($path, $parameters, $requestHeaders); - } - - /** - * {@inheritDoc} - */ - public function put($path, array $parameters = array(), $requestHeaders = array()) - { - return parent::put($path, $parameters, $requestHeaders); - } - - /** - * {@inheritDoc} - */ - public function delete($path, array $parameters = array(), $requestHeaders = array()) - { - return parent::delete($path, $parameters, $requestHeaders); - } -} diff --git a/test/Gitlab/Tests/Api/ApiTestCase.php b/test/Gitlab/Tests/Api/ApiTestCase.php deleted file mode 100644 index a695d97cb..000000000 --- a/test/Gitlab/Tests/Api/ApiTestCase.php +++ /dev/null @@ -1,23 +0,0 @@ -getClientMock(); - - $methods = array_merge(array('get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head'), $methods); - - return $this->getMockBuilder($this->getApiClass()) - ->setMethods($methods) - ->setConstructorArgs(array($client)) - ->getMock() - ; - } -} diff --git a/test/Gitlab/Tests/Api/DeployKeysTest.php b/test/Gitlab/Tests/Api/DeployKeysTest.php index a17dd312c..9ea069487 100644 --- a/test/Gitlab/Tests/Api/DeployKeysTest.php +++ b/test/Gitlab/Tests/Api/DeployKeysTest.php @@ -1,6 +1,6 @@ getHttpMock()); - } + abstract protected function getApiClass(); - /** - * @return \PHPUnit_Framework_MockObject_MockObject|HttpClientInterface - */ - protected function getHttpMock() - { - return $this->getMock('Gitlab\HttpClient\HttpClient', array(), array(null, array(), $this->getHttpClientMock())); - } /** - * @return \PHPUnit_Framework_MockObject_MockObject|Curl + * @param array $methods + * + * @return \PHPUnit_Framework_MockObject_MockObject */ - protected function getHttpClientMock() + protected function getApiMock(array $methods = []) { - $httpClient = $this->getMock('Buzz\Client\Curl', array('send')); + $httpClient = $this->getMockBuilder(HttpClient::class) + ->setMethods(array('sendRequest')) + ->getMock(); $httpClient ->expects($this->any()) - ->method('send'); + ->method('sendRequest'); + + $client = Client::createWithHttpClient($httpClient); - return $httpClient; + return $this->getMockBuilder($this->getApiClass()) + ->setMethods(array_merge(array('get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head'), $methods)) + ->setConstructorArgs(array($client)) + ->getMock(); } } diff --git a/test/Gitlab/Tests/Api/UsersTest.php b/test/Gitlab/Tests/Api/UsersTest.php index a1441da00..de2932396 100644 --- a/test/Gitlab/Tests/Api/UsersTest.php +++ b/test/Gitlab/Tests/Api/UsersTest.php @@ -2,7 +2,7 @@ use Gitlab\Api\AbstractApi; -class UsersTest extends ApiTestCase +class UsersTest extends TestCase { /** * @test diff --git a/test/Gitlab/Tests/HttpClient/BuilderTest.php b/test/Gitlab/Tests/HttpClient/BuilderTest.php new file mode 100644 index 000000000..fa51bdd2f --- /dev/null +++ b/test/Gitlab/Tests/HttpClient/BuilderTest.php @@ -0,0 +1,54 @@ + + */ +class BuilderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Builder + */ + private $subject; + + public function setUp() + { + $this->subject = new Builder( + $this->getMock(HttpClient::class), + $this->getMock(RequestFactory::class), + $this->getMock(StreamFactory::class) + ); + } + + public function testAddPluginShouldInvalidateHttpClient() + { + $client = $this->subject->getHttpClient(); + + $this->subject->addPlugin($this->getMock(Plugin::class)); + + $this->assertNotSame($client, $this->subject->getHttpClient()); + } + + public function testRemovePluginShouldInvalidateHttpClient() + { + $this->subject->addPlugin($this->getMock(Plugin::class)); + + $client = $this->subject->getHttpClient(); + + $this->subject->removePlugin(Plugin::class); + + $this->assertNotSame($client, $this->subject->getHttpClient()); + } + + public function testHttpClientShouldBeAnHttpMethodsClient() + { + $this->assertInstanceOf(HttpMethodsClient::class, $this->subject->getHttpClient()); + } +} diff --git a/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php b/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php new file mode 100644 index 000000000..661e6ad6a --- /dev/null +++ b/test/Gitlab/Tests/HttpClient/Message/ResponseMediatorTest.php @@ -0,0 +1,77 @@ + + */ +class ResponseMediatorTest extends \PHPUnit_Framework_TestCase +{ + public function testGetContent() + { + $body = array('foo' => 'bar'); + $response = new Response( + 200, + array('Content-Type'=>'application/json'), + \GuzzleHttp\Psr7\stream_for(json_encode($body)) + ); + + $this->assertEquals($body, ResponseMediator::getContent($response)); + } + + /** + * If content-type is not json we should get the raw body. + */ + public function testGetContentNotJson() + { + $body = 'foobar'; + $response = new Response( + 200, + array(), + \GuzzleHttp\Psr7\stream_for($body) + ); + + $this->assertEquals($body, ResponseMediator::getContent($response)); + } + + /** + * Make sure we return the body if we have invalid json + */ + public function testGetContentInvalidJson() + { + $body = 'foobar'; + $response = new Response( + 200, + array('Content-Type'=>'application/json'), + \GuzzleHttp\Psr7\stream_for($body) + ); + + $this->assertEquals($body, ResponseMediator::getContent($response)); + } + + public function testGetPagination() + { + $header = <<; rel="first", +; rel="next", +; rel="prev", +; rel="last", +TEXT; + + $pagination = array( + 'first' => 'https://example.gitlab.com', + 'next' => 'https://example.gitlab.com', + 'prev' => 'https://example.gitlab.com', + 'last' => 'https://example.gitlab.com' + ); + + // response mock + $response = new Response(200, array('link'=>$header)); + $result = ResponseMediator::getPagination($response); + + $this->assertEquals($pagination, $result); + } +} diff --git a/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php b/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php new file mode 100644 index 000000000..805151d2c --- /dev/null +++ b/test/Gitlab/Tests/HttpClient/Plugin/ApiVersionTest.php @@ -0,0 +1,67 @@ +getMockBuilder(\stdClass::class) + ->setMethods(['next']) + ->getMock() + ; + $callback->expects($this->once()) + ->method('next') + ->with($this->isInstanceOf(RequestInterface::class)) + ->willReturn($promise) + ; + + $this->assertEquals($promise, $plugin->handleRequest($request, [$callback, 'next'], function () {})); + } + + public function testPrefixRequestPath() + { + $request = new Request('GET', 'projects'); + $expected = new Request('GET', '/api/v3/projects'); + $plugin = new ApiVersion(); + + $callback = $this->getMockBuilder(\stdClass::class) + ->setMethods(['next']) + ->getMock() + ; + $callback->expects($this->once()) + ->method('next') + ->with($expected) + ; + + $plugin->handleRequest($request, [$callback, 'next'], function () {}); + } + + public function testNoPrefixingRequired() + { + $request = new Request('GET', '/api/v3/projects'); + $plugin = new ApiVersion(); + + $callback = $this->getMockBuilder(\stdClass::class) + ->setMethods(['next']) + ->getMock() + ; + $callback->expects($this->once()) + ->method('next') + ->with($request) + ; + + $plugin->handleRequest($request, [$callback, 'next'], function () {}); + } +} diff --git a/test/Gitlab/Tests/ResultPagerTest.php b/test/Gitlab/Tests/ResultPagerTest.php new file mode 100644 index 000000000..b411b0704 --- /dev/null +++ b/test/Gitlab/Tests/ResultPagerTest.php @@ -0,0 +1,113 @@ +getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $api = $this->getMockBuilder(ApiInterface::class) + ->setMethods(['__construct', 'all']) + ->getMock() + ; + $api->expects($this->once()) + ->method('all') + ->willReturn(['project1', 'project2']) + ; + + $pager = new ResultPager($client); + + $result = $pager->fetch($api, 'all'); + + $this->assertEquals(['project1', 'project2'], $result); + } + + public function testFetchAll() + { + $client = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $history = $this->getMockBuilder(History::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $response1 = (new Response)->withHeader('Link', '; rel="next",'); + $response2 = (new Response)->withHeader('Link', '; rel="next",') + ->withHeader('Content-Type', 'application/json') + ->withBody(stream_for('["project3", "project4"]')) + ; + $response3 = (new Response)->withHeader('Content-Type', 'application/json') + ->withBody(stream_for('["project5", "project6"]')) + ; + + $history + ->method('getLastResponse') + ->will($this->onConsecutiveCalls( + $response1, + $response1, + $response1, + $response2, + $response2, + $response2, + $response3 + )) + ; + + $httpClient = $this->getMockBuilder(HttpMethodsClient::class) + ->disableOriginalConstructor() + ->getMock() + ; + + $httpClient->expects($this->exactly(2)) + ->method('get') + ->withConsecutive( + ['https://example.gitlab.com/projects?page=2'], + ['https://example.gitlab.com/projects?page=3'] + ) + ->will($this->onConsecutiveCalls( + $response2, + $response3 + )) + ; + + $client + ->method('getResponseHistory') + ->willReturn($history) + ; + $client + ->method('getHttpClient') + ->willReturn($httpClient) + ; + + $api = $this->getMockBuilder(ApiInterface::class) + ->setMethods(['__construct', 'all']) + ->getMock(); + $api->expects($this->exactly(1)) + ->method('all') + ->willReturn(['project1', 'project2']) + ; + + $pager = new ResultPager($client); + + $result = $pager->fetchAll($api, 'all'); + + $this->assertEquals(['project1', 'project2', 'project3', 'project4', 'project5', 'project6'], $result); + } +}