diff --git a/.styleci.yml b/.styleci.yml index 418a93eb0f1..731de4d858d 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,5 +1,4 @@ preset: psr2 enabled: - - long_array_syntax - return diff --git a/.travis.yml b/.travis.yml index 936fc3d9441..30c91afd2f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: php php: - - 5.3 - - 5.4 - 5.5 - 5.6 - 7.0 diff --git a/README.md b/README.md index fec2f0fd9f6..0d8e23466b1 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Uses [GitHub API v3](http://developer.github.com/v3/). The object API is very si ## Features -* Follows PSR-0 conventions and coding standard: autoload friendly +* Follows PSR-4 conventions and coding standard: autoload friendly * Light and fast thanks to lazy loading of API classes * Extensively tested and documented ## Requirements -* PHP >= 5.3.2 with [cURL](http://php.net/manual/en/book.curl.php) extension, +* PHP >= 5.5 * [Guzzle](https://github.com/guzzle/guzzle) library, * (optional) PHPUnit to run tests. @@ -28,21 +28,12 @@ The first step to use `php-github-api` is to download composer: $ curl -s http://getcomposer.org/installer | php ``` -Then we have to install our dependencies using: +Then run the following command to require the library: ```bash -$ php composer.phar install -``` -Now we can use autoloader from Composer by: - -```json -{ - "require": { - "knplabs/github-api": "~1.4" - } -} +$ php composer.phar require knplabs/github-api php-http/guzzle6-adapter ``` -> `php-github-api` follows the PSR-4 convention names for its classes, which means you can easily integrate `php-github-api` classes loading in your own autoloader. +Why `php-http/guzzle6-adapter`? We are decoupled form any HTTP messaging client with help by [HTTPlug](http://httplug.io/). Read about clients in our [docs](doc/customize.md). ## Using Laravel? @@ -70,19 +61,15 @@ From `$client` object, you can access to all GitHub. // This file is generated by Composer require_once 'vendor/autoload.php'; -$client = new \Github\Client( - new \Github\HttpClient\CachedHttpClient(array('cache_dir' => '/tmp/github-api-cache')) -); +$client = new \Github\Client(); +$client->useCache(); // Or select directly which cache you want to use -$client = new \Github\HttpClient\CachedHttpClient(); -$client->setCache( +$client->useCache( // Built in one, or any cache implementing this interface: // Github\HttpClient\Cache\CacheInterface new \Github\HttpClient\Cache\FilesystemCache('/tmp/github-api-cache') ); - -$client = new \Github\Client($client); ``` Using cache, the client will get cached responses if resources haven't changed since last time, diff --git a/composer.json b/composer.json index 8e57dd236cb..a905ca9500b 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,16 @@ } ], "require": { - "php": ">=5.3.2", - "ext-curl": "*", - "guzzle/guzzle": "~3.7" + "php": "^5.5|^7.0", + "php-http/httplug": "^1.0", + "php-http/discovery": "^1.0", + "php-http/client-implementation": "^1.0", + "php-http/client-common": "^1.1" }, "require-dev": { "phpunit/phpunit": "~4.0", + "php-http/guzzle6-adapter": "~1.0", + "guzzlehttp/psr7": "^1.2", "sllh/php-cs-fixer-styleci-bridge": "~1.3" }, "suggest": { diff --git a/doc/customize.md b/doc/customize.md index 82274dc5e4a..9327afb4858 100644 --- a/doc/customize.md +++ b/doc/customize.md @@ -1,60 +1,43 @@ ## Customize `php-github-api` and testing [Back to the navigation](README.md) -### Configure the http client -Wanna change, let's say, the http client User Agent? +### Inject a new HTTP client instance -```php -$client->getHttpClient()->setOption('user_agent', 'My new User Agent'); -``` - -See all available options in `Github/HttpClient/HttpClient.php` - -### Guzzle events +`php-github-api` relies on `php-http/discovery` to find an installed HTTP client. You may specify a HTTP client +yourself by calling `\Github\Client::setHttpClient`. A HTTP client must implement `Http\Client\HttpClient`. A list of +community provided clients is found here: https://packagist.org/providers/php-http/client-implementation -If you need to perform any special action on request/response use guzzle events: +You can inject a HTTP client through `Github\Client#setHttpClient()` method: ```php -use Guzzle\Common\Event; -use Github\HttpClient\Message\ResponseMediator; - -$client->getHttpClient()->addListener('request.success', function(Event $event) { - $remaining = ResponseMediator::getApiLimit($event['response']); - - var_dump($remaining); -}); - -$client->user()->show('cursedcoder'); +$client = new Github\Client(); +$client->setHttpClient(new Http\Adapter\Guzzle6\Client()); ``` -see list of events http://guzzle3.readthedocs.org/http-client/request.html#plugins-and-events +### Configure the HTTP client -### Inject a new http client instance - -`php-github-api` provides a curl-based implementation of a http client. -If you want to use your own http client implementation, inject it to the `Github\Client` instance: +Wanna change, let's say, the HTTP client User Agent? You need to create a Plugin that modifies the +request. Read more about [HTTPlug plugins here](http://docs.php-http.org/en/latest/plugins/introduction.html#how-it-works). ```php -use Github\HttpClient\HttpClient; +use Http\Client\Common\Plugin; +use Psr\Http\Message\RequestInterface; -// create a custom http client -class MyHttpClient extends HttpClient +class CustomUserAgentPlugin implements Plugin { - public function request($url, array $parameters = array(), $httpMethod = 'GET', array $headers = array()) + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) { - // send the request and return the raw response + $request->withHeader('user-agent', 'Foobar'); + + return $next($request); } } -``` - -> Your http client implementation may not extend `Github\HttpClient\HttpClient`, but only implement `Github\HttpClient\HttpClientInterface`. -You can now inject your http client through `Github\Client#setHttpClient()` method: - -```php -$client = new Github\Client(); -$client->setHttpClient(new MyHttpClient()); +$githubClient->addPlugin(new CustomUserAgentPlugin()); ``` ### Run Test Suite diff --git a/lib/Github/Api/AbstractApi.php b/lib/Github/Api/AbstractApi.php index e7209936ea5..bb2e1c7855b 100644 --- a/lib/Github/Api/AbstractApi.php +++ b/lib/Github/Api/AbstractApi.php @@ -63,7 +63,7 @@ public function setPerPage($perPage) * @param array $parameters GET parameters. * @param array $requestHeaders Request Headers. * - * @return \Guzzle\Http\EntityBodyInterface|mixed|string + * @return array|string */ protected function get($path, array $parameters = array(), $requestHeaders = array()) { @@ -73,7 +73,12 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr if (array_key_exists('ref', $parameters) && is_null($parameters['ref'])) { unset($parameters['ref']); } - $response = $this->client->getHttpClient()->get($path, $parameters, $requestHeaders); + + if (count($parameters) > 0) { + $path .= '?'.http_build_query($parameters); + } + + $response = $this->client->getHttpClient()->get($path, $requestHeaders); return ResponseMediator::getContent($response); } @@ -85,7 +90,7 @@ protected function get($path, array $parameters = array(), $requestHeaders = arr * @param array $parameters HEAD parameters. * @param array $requestHeaders Request headers. * - * @return \Guzzle\Http\Message\Response + * @return \Psr\Http\Message\ResponseInterface */ protected function head($path, array $parameters = array(), $requestHeaders = array()) { @@ -93,9 +98,7 @@ protected function head($path, array $parameters = array(), $requestHeaders = ar unset($parameters['ref']); } - $response = $this->client->getHttpClient()->request($path, null, 'HEAD', $requestHeaders, array( - 'query' => $parameters - )); + $response = $this->client->getHttpClient()->head($path.'?'.http_build_query($parameters), $requestHeaders); return $response; } @@ -120,17 +123,17 @@ protected function post($path, array $parameters = array(), $requestHeaders = ar * Send a POST request with raw data. * * @param string $path Request path. - * @param $body Request body. + * @param string $body Request body. * @param array $requestHeaders Request headers. * - * @return \Guzzle\Http\EntityBodyInterface|mixed|string + * @return array|string */ protected function postRaw($path, $body, $requestHeaders = array()) { $response = $this->client->getHttpClient()->post( $path, - $body, - $requestHeaders + $requestHeaders, + $body ); return ResponseMediator::getContent($response); @@ -147,8 +150,8 @@ protected function patch($path, array $parameters = array(), $requestHeaders = a { $response = $this->client->getHttpClient()->patch( $path, - $this->createJsonBody($parameters), - $requestHeaders + $requestHeaders, + $this->createJsonBody($parameters) ); return ResponseMediator::getContent($response); @@ -165,8 +168,8 @@ protected function put($path, array $parameters = array(), $requestHeaders = arr { $response = $this->client->getHttpClient()->put( $path, - $this->createJsonBody($parameters), - $requestHeaders + $requestHeaders, + $this->createJsonBody($parameters) ); return ResponseMediator::getContent($response); @@ -183,8 +186,8 @@ protected function delete($path, array $parameters = array(), $requestHeaders = { $response = $this->client->getHttpClient()->delete( $path, - $this->createJsonBody($parameters), - $requestHeaders + $requestHeaders, + $this->createJsonBody($parameters) ); return ResponseMediator::getContent($response); diff --git a/lib/Github/Api/AcceptHeaderTrait.php b/lib/Github/Api/AcceptHeaderTrait.php new file mode 100644 index 00000000000..039f3f4c4f6 --- /dev/null +++ b/lib/Github/Api/AcceptHeaderTrait.php @@ -0,0 +1,62 @@ + + */ +trait AcceptHeaderTrait +{ + protected $acceptHeaderValue = null; + + protected function get($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::get($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function head($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::head($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function post($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::post($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function postRaw($path, $body, $requestHeaders = array()) + { + return parent::postRaw($path, $body, $this->mergeHeaders($requestHeaders)); + } + + protected function patch($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::patch($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function put($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::put($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + protected function delete($path, array $parameters = array(), $requestHeaders = array()) + { + return parent::delete($path, $parameters, $this->mergeHeaders($requestHeaders)); + } + + /** + * Append a new accept header on all requests + * @return array + */ + private function mergeHeaders(array $headers = array()) + { + $default = array(); + if ($this->acceptHeaderValue) { + $default = array('Accept' => $this->acceptHeaderValue); + } + + return array_merge($default, $headers); + } +} diff --git a/lib/Github/Api/Enterprise/ManagementConsole.php b/lib/Github/Api/Enterprise/ManagementConsole.php index 8554425f365..bc25e53466f 100644 --- a/lib/Github/Api/Enterprise/ManagementConsole.php +++ b/lib/Github/Api/Enterprise/ManagementConsole.php @@ -68,7 +68,7 @@ public function keys($hash) * @param string $uri the request URI * @param string $hash md5 hash of your license * - * @return \Guzzle\Http\EntityBodyInterface|mixed|string + * @return array|string */ protected function getWithLicenseHash($uri, $hash) { diff --git a/lib/Github/Api/GitData/Blobs.php b/lib/Github/Api/GitData/Blobs.php index fe9cfc6123a..7ce05b31fab 100644 --- a/lib/Github/Api/GitData/Blobs.php +++ b/lib/Github/Api/GitData/Blobs.php @@ -3,14 +3,18 @@ namespace Github\Api\GitData; use Github\Api\AbstractApi; +use Github\Api\AcceptHeaderTrait; use Github\Exception\MissingArgumentException; /** * @link http://developer.github.com/v3/git/blobs/ * @author Joseph Bielawski + * @author Tobias Nyholm */ class Blobs extends AbstractApi { + use AcceptHeaderTrait; + /** * Configure the Acccept header depending on the blob type. * @@ -18,10 +22,8 @@ class Blobs extends AbstractApi */ public function configure($bodyType = null) { - if ('raw' == $bodyType) { - $this->client->setHeaders(array( - 'Accept' => sprintf('application/vnd.github.%s.raw', $this->client->getOption('api_version')) - )); + if ('raw' === $bodyType) { + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.raw', $this->client->getOption('api_version')); } } diff --git a/lib/Github/Api/Issue/Comments.php b/lib/Github/Api/Issue/Comments.php index ad6a95d633b..a8e6825d892 100644 --- a/lib/Github/Api/Issue/Comments.php +++ b/lib/Github/Api/Issue/Comments.php @@ -3,14 +3,18 @@ namespace Github\Api\Issue; use Github\Api\AbstractApi; +use Github\Api\AcceptHeaderTrait; use Github\Exception\MissingArgumentException; /** * @link http://developer.github.com/v3/issues/comments/ * @author Joseph Bielawski + * @author Tobias Nyholm */ class Comments extends AbstractApi { + use AcceptHeaderTrait; + /** * Configure the body type. * @@ -23,9 +27,7 @@ public function configure($bodyType = null) $bodyType = 'full'; } - $this->client->setHeaders(array( - sprintf('Accept: application/vnd.github.%s.%s+json', $this->client->getOption('api_version'), $bodyType) - )); + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.%s+json', $this->client->getOption('api_version'), $bodyType); } /** diff --git a/lib/Github/Api/Repository/Assets.php b/lib/Github/Api/Repository/Assets.php index b386561796e..7fa7a1dddfc 100644 --- a/lib/Github/Api/Repository/Assets.php +++ b/lib/Github/Api/Repository/Assets.php @@ -73,13 +73,7 @@ public function create($username, $repository, $id, $name, $contentType, $conten // Asset creation requires a separate endpoint, uploads.github.com. // Change the base url for the HTTP client temporarily while we execute // this request. - $baseUrl = $this->client->getHttpClient()->client->getBaseUrl(); - $this->client->getHttpClient()->client->setBaseUrl('https://uploads.github.com/'); - - $response = $this->postRaw('repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets?name='.$name, $content, array('Content-Type' => $contentType)); - - // Reset the base url. - $this->client->getHttpClient()->client->setBaseUrl($baseUrl); + $response = $this->postRaw('https://uploads.github.com/repos/'.rawurlencode($username).'/'.rawurlencode($repository).'/releases/'.rawurlencode($id).'/assets?name='.$name, $content, array('Content-Type' => $contentType)); return $response; } diff --git a/lib/Github/Api/Repository/Comments.php b/lib/Github/Api/Repository/Comments.php index cce0e5059d6..d3fe8a09381 100644 --- a/lib/Github/Api/Repository/Comments.php +++ b/lib/Github/Api/Repository/Comments.php @@ -3,14 +3,18 @@ namespace Github\Api\Repository; use Github\Api\AbstractApi; +use Github\Api\AcceptHeaderTrait; use Github\Exception\MissingArgumentException; /** * @link http://developer.github.com/v3/repos/comments/ * @author Joseph Bielawski + * @author Tobias Nyholm */ class Comments extends AbstractApi { + use AcceptHeaderTrait; + public function configure($bodyType = null) { switch ($bodyType) { @@ -30,7 +34,7 @@ public function configure($bodyType = null) $header = sprintf('Accept: application/vnd.github.%s.full+json', $this->client->getOption('api_version')); } - $this->client->setHeaders(array($header)); + $this->acceptHeaderValue = $header; } public function all($username, $repository, $sha = null) diff --git a/lib/Github/Api/Repository/Stargazers.php b/lib/Github/Api/Repository/Stargazers.php index f8d2ec17e14..f1d54db8fc2 100644 --- a/lib/Github/Api/Repository/Stargazers.php +++ b/lib/Github/Api/Repository/Stargazers.php @@ -3,13 +3,17 @@ namespace Github\Api\Repository; use Github\Api\AbstractApi; +use Github\Api\AcceptHeaderTrait; /** * @link https://developer.github.com/v3/activity/starring/#list-stargazers * @author Nicolas Dupont + * @author Tobias Nyholm */ class Stargazers extends AbstractApi { + use AcceptHeaderTrait; + /** * Configure the body type * @@ -20,11 +24,7 @@ class Stargazers extends AbstractApi public function configure($bodyType = null) { if ('star' === $bodyType) { - $this->client->setHeaders( - array( - 'Accept' => sprintf('application/vnd.github.%s.star+json', $this->client->getOption('api_version')) - ) - ); + $this->acceptHeaderValue = sprintf('application/vnd.github.%s.star+json', $this->client->getOption('api_version')); } } diff --git a/lib/Github/Client.php b/lib/Github/Client.php index b3c9f63e266..9279ce5c4fd 100644 --- a/lib/Github/Client.php +++ b/lib/Github/Client.php @@ -5,8 +5,20 @@ use Github\Api\ApiInterface; use Github\Exception\InvalidArgumentException; use Github\Exception\BadMethodCallException; -use Github\HttpClient\HttpClient; -use Github\HttpClient\HttpClientInterface; +use Github\HttpClient\Cache\CacheInterface; +use Github\HttpClient\Plugin\Authentication; +use Github\HttpClient\Plugin\Cache; +use Github\HttpClient\Plugin\GithubExceptionThrower; +use Github\HttpClient\Plugin\History; +use Github\HttpClient\Plugin\PathPrepend; +use Http\Client\Common\HttpMethodsClient; +use Http\Client\Common\Plugin; +use Http\Client\Common\PluginClient; +use Http\Client\HttpClient; +use Http\Discovery\HttpClientDiscovery; +use Http\Discovery\MessageFactoryDiscovery; +use Http\Discovery\UriFactoryDiscovery; +use Http\Message\MessageFactory; /** * Simple yet very cool PHP GitHub client. @@ -77,32 +89,67 @@ class Client * @var array */ private $options = array( - 'base_url' => 'https://api.github.com/', - - 'user_agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', - 'timeout' => 10, - - 'api_limit' => 5000, 'api_version' => 'v3', - - 'cache_dir' => null ); /** - * The Buzz instance used to communicate with GitHub. + * The object that sends HTTP messages * * @var HttpClient */ private $httpClient; + /** + * A HTTP client with all our plugins + * + * @var PluginClient + */ + private $pluginClient; + + /** + * @var MessageFactory + */ + private $messageFactory; + + /** + * @var Plugin[] + */ + private $plugins = []; + + /** + * True if we should create a new Plugin client at next request. + * @var bool + */ + private $httpClientModified = true; + + /** + * Http headers + * @var array + */ + private $headers = []; + + /** + * @var History + */ + private $responseHistory; + /** * Instantiate a new GitHub client. * - * @param null|HttpClientInterface $httpClient Github http client + * @param HttpClient|null $httpClient */ - public function __construct(HttpClientInterface $httpClient = null) + public function __construct(HttpClient $httpClient = null) { - $this->httpClient = $httpClient; + $this->httpClient = $httpClient ?: HttpClientDiscovery::find(); + $this->messageFactory = MessageFactoryDiscovery::find(); + + $this->responseHistory = new History(); + $this->addPlugin(new GithubExceptionThrower()); + $this->addPlugin(new Plugin\HistoryPlugin($this->responseHistory)); + $this->addPlugin(new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri('https://api.github.com/'))); + $this->addPlugin(new Plugin\HeaderDefaultsPlugin(array( + 'User-Agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', + ))); } /** @@ -235,7 +282,8 @@ public function authenticate($tokenOrLogin, $password = null, $authMethod = null $authMethod = self::AUTH_HTTP_PASSWORD; } - $this->getHttpClient()->authenticate($tokenOrLogin, $password, $authMethod); + $this->removePlugin(Authentication::class); + $this->addPlugin(new Authentication($tokenOrLogin, $password, $authMethod)); } /** @@ -245,27 +293,60 @@ public function authenticate($tokenOrLogin, $password = null, $authMethod = null */ public function setEnterpriseUrl($enterpriseUrl) { - $baseUrl = (substr($enterpriseUrl, -1) == '/') ? substr($enterpriseUrl, 0, -1) : $enterpriseUrl; - $this->getHttpClient()->client->setBaseUrl($baseUrl . '/api/v3'); + $this->removePlugin(Plugin\AddHostPlugin::class); + $this->removePlugin(PathPrepend::class); + + $this->addPlugin(new Plugin\AddHostPlugin(UriFactoryDiscovery::find()->createUri($enterpriseUrl))); + $this->addPlugin(new PathPrepend(sprintf('/api/%s/', $this->getOption('api_version')))); + } + + /** + * Add a new plugin to the chain + * + * @param Plugin $plugin + */ + protected function addPlugin(Plugin $plugin) + { + $this->plugins[] = $plugin; + $this->httpClientModified = true; } /** - * @return HttpClient + * Remove a plugin by its fqn. + * + * @param string $fqn + */ + protected function removePlugin($fqn) + { + foreach ($this->plugins as $idx => $plugin) { + if ($plugin instanceof $fqn) { + unset($this->plugins[$idx]); + } + } + } + + /** + * @return HttpMethodsClient */ public function getHttpClient() { - if (null === $this->httpClient) { - $this->httpClient = new HttpClient($this->options); + if ($this->httpClientModified) { + $this->httpClientModified = false; + $this->pluginClient = new HttpMethodsClient( + new PluginClient($this->httpClient, $this->plugins), + $this->messageFactory + ); } - return $this->httpClient; + return $this->pluginClient; } /** - * @param HttpClientInterface $httpClient + * @param HttpClient $httpClient */ - public function setHttpClient(HttpClientInterface $httpClient) + public function setHttpClient(HttpClient $httpClient) { + $this->httpClientModified = true; $this->httpClient = $httpClient; } @@ -274,15 +355,39 @@ public function setHttpClient(HttpClientInterface $httpClient) */ public function clearHeaders() { - $this->getHttpClient()->clearHeaders(); + $this->headers = array( + 'Accept' => sprintf('application/vnd.github.%s+json', $this->options['api_version']), + ); + + $this->removePlugin(Plugin\HeaderAppendPlugin::class); + $this->addPlugin(new Plugin\HeaderAppendPlugin($this->headers)); } /** * @param array $headers */ - public function setHeaders(array $headers) + public function addHeaders(array $headers) { - $this->getHttpClient()->setHeaders($headers); + $this->headers = array_merge($this->headers, $headers); + + $this->removePlugin(Plugin\HeaderAppendPlugin::class); + $this->addPlugin(new Plugin\HeaderAppendPlugin($this->headers)); + } + + /** + * @param bool|CacheInterface $cache + */ + public function useCache($cache = true) + { + $this->removePlugin(Cache::class); + if ($cache !== false) { + if ($cache instanceof CacheInterface) { + $plugin = new Cache($cache); + } else { + $plugin = new Cache(); + } + $this->addPlugin($plugin); + } } /** @@ -332,4 +437,13 @@ public function __call($name, $args) throw new BadMethodCallException(sprintf('Undefined method called: "%s"', $name)); } } + + /** + * + * @return null|\Psr\Http\Message\ResponseInterface + */ + public function getLastResponse() + { + return $this->responseHistory->getLastResponse(); + } } diff --git a/lib/Github/HttpClient/Cache/CacheInterface.php b/lib/Github/HttpClient/Cache/CacheInterface.php index 77bb4f89ec9..014e0d20626 100644 --- a/lib/Github/HttpClient/Cache/CacheInterface.php +++ b/lib/Github/HttpClient/Cache/CacheInterface.php @@ -2,7 +2,7 @@ namespace Github\HttpClient\Cache; -use Guzzle\Http\Message\Response; +use Psr\Http\Message\ResponseInterface; /** * Caches github api responses. @@ -37,15 +37,15 @@ public function getETag($id); * * @throws \InvalidArgumentException If cache data don't exists * - * @return Response The cached response object + * @return ResponseInterface The cached response object */ public function get($id); /** * @param string $id The id of the cached resource - * @param Response $response The response to cache + * @param ResponseInterface $response The response to cache * * @throws \InvalidArgumentException If cache data cannot be saved */ - public function set($id, Response $response); + public function set($id, ResponseInterface $response); } diff --git a/lib/Github/HttpClient/Cache/FilesystemCache.php b/lib/Github/HttpClient/Cache/FilesystemCache.php index ff332b57075..82581b62a75 100644 --- a/lib/Github/HttpClient/Cache/FilesystemCache.php +++ b/lib/Github/HttpClient/Cache/FilesystemCache.php @@ -2,7 +2,7 @@ namespace Github\HttpClient\Cache; -use Guzzle\Http\Message\Response; +use Psr\Http\Message\ResponseInterface; class FilesystemCache implements CacheInterface { @@ -25,7 +25,7 @@ public function __construct($path) public function get($id) { if (false !== $content = @file_get_contents($this->getPath($id))) { - return unserialize($content); + return ResponseSerializer::unserialize($content); } throw new \InvalidArgumentException(sprintf('File "%s" not found', $this->getPath($id))); @@ -34,13 +34,13 @@ public function get($id) /** * {@inheritdoc} */ - public function set($id, Response $response) + public function set($id, ResponseInterface $response) { if (!is_dir($this->path)) { @mkdir($this->path, 0777, true); } - if (false === @file_put_contents($this->getPath($id), serialize($response))) { + if (false === @file_put_contents($this->getPath($id), ResponseSerializer::serialize($response))) { throw new \InvalidArgumentException(sprintf('Cannot put content in file "%s"', $this->getPath($id))); } if (false === @file_put_contents($this->getPath($id).'.etag', $response->getHeader('ETag'))) { diff --git a/lib/Github/HttpClient/Cache/GaufretteCache.php b/lib/Github/HttpClient/Cache/GaufretteCache.php index b72a104c9af..812be7cbbb2 100644 --- a/lib/Github/HttpClient/Cache/GaufretteCache.php +++ b/lib/Github/HttpClient/Cache/GaufretteCache.php @@ -2,8 +2,8 @@ namespace Github\HttpClient\Cache; -use Guzzle\Http\Message\Response; use Gaufrette\Filesystem; +use Psr\Http\Message\ResponseInterface; /** * Gaufrette Cache. @@ -32,15 +32,15 @@ public function get($id) { $content = $this->filesystem->read($id); - return unserialize($content); + return ResponseSerializer::unserialize($content); } /** * {@inheritdoc} */ - public function set($id, Response $response) + public function set($id, ResponseInterface $response) { - $this->filesystem->write($id, serialize($response), true); + $this->filesystem->write($id, ResponseSerializer::serialize($response), true); $this->filesystem->write($id.'.etag', $response->getHeader('ETag'), true); } diff --git a/lib/Github/HttpClient/Cache/ResponseSerializer.php b/lib/Github/HttpClient/Cache/ResponseSerializer.php new file mode 100644 index 00000000000..43b8d95fa05 --- /dev/null +++ b/lib/Github/HttpClient/Cache/ResponseSerializer.php @@ -0,0 +1,61 @@ + + */ +class ResponseSerializer +{ + /** + * @param ResponseInterface $response + * @param StreamFactory|null $streamFactory + * + * @return array + */ + public static function serialize(ResponseInterface $response, StreamFactory $streamFactory = null) + { + $streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + + $bodyStream = $response->getBody(); + $body = $bodyStream->__toString(); + if ($bodyStream->isSeekable()) { + $bodyStream->rewind(); + } else { + /* + * If the body is not seekbable we can not rewind it. The stream could be a type of stream + * that you only can read once. That is why we have to replace the old stream with a new one. + */ + $response = $response->withBody($streamFactory->createStream($body)); + } + + return serialize(array('response' => serialize($response), 'body' => $body)); + } + + /** + * @param $data + * @param StreamFactory|null $streamFactory + * + * @return ResponseInterface|null + */ + public static function unserialize($serializedData, StreamFactory $streamFactory = null) + { + $data = unserialize($serializedData); + if (!isset($data['response']) || !isset($data['body'])) { + return null; + } + + $streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + + $response = unserialize($data['response']); + $response = $response->withBody($streamFactory->createStream($data['body'])); + + return $response; + } +} diff --git a/lib/Github/HttpClient/CachedHttpClient.php b/lib/Github/HttpClient/CachedHttpClient.php deleted file mode 100644 index 224ebd11235..00000000000 --- a/lib/Github/HttpClient/CachedHttpClient.php +++ /dev/null @@ -1,124 +0,0 @@ - - */ -class CachedHttpClient extends HttpClient -{ - /** - * @var CacheInterface - */ - protected $cache; - - /** - * Contains the lastResponse fetched from cache. - * - * @var \Guzzle\Http\Message\Response - */ - private $lastCachedResponse; - - /** - * Identifier used for the cache file(s). - * $path + encoded query parameter(s) if they exist. - * - * @var string - */ - private $id; - - /** - * @return CacheInterface - */ - public function getCache() - { - if (null === $this->cache) { - $this->cache = new FilesystemCache($this->options['cache_dir'] ?: sys_get_temp_dir().DIRECTORY_SEPARATOR.'php-github-api-cache'); - } - - return $this->cache; - } - - /** - * @param $cache CacheInterface - */ - public function setCache(CacheInterface $cache) - { - $this->cache = $cache; - } - - /** - * {@inheritdoc} - */ - public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) - { - $response = parent::request($path, $body, $httpMethod, $headers, $options); - - if (304 == $response->getStatusCode()) { - $cacheResponse = $this->getCache()->get($this->id); - $this->lastCachedResponse = $cacheResponse; - - return $cacheResponse; - } - - if (in_array($httpMethod, array('GET', 'HEAD'), true)) { - $this->getCache()->set($this->id, $response); - } - - return $response; - } - - /** - * Create requests with If-Modified-Since headers. - * - * {@inheritdoc} - */ - protected function createRequest($httpMethod, $path, $body = null, array $headers = array(), array $options = array()) - { - $request = parent::createRequest($httpMethod, $path, $body, $headers, $options); - - $this->id = $path; - - if (array_key_exists('query', $options) && !empty($options['query'])) { - $this->id .= '?' . $request->getQuery(); - } - - if ($modifiedAt = $this->getCache()->getModifiedSince($this->id)) { - $modifiedAt = new \DateTime('@'.$modifiedAt); - $modifiedAt->setTimezone(new \DateTimeZone('GMT')); - - $request->addHeader( - 'If-Modified-Since', - sprintf('%s GMT', $modifiedAt->format('l, d-M-y H:i:s')) - ); - } - if ($etag = $this->getCache()->getETag($this->id)) { - $request->addHeader( - 'If-None-Match', - $etag - ); - } - - return $request; - } - - /** - * @return \Guzzle\Http\Message\Response - */ - public function getLastResponse($force = false) - { - $lastResponse = parent::getLastResponse(); - if (304 != $lastResponse->getStatusCode()) { - $force = true; - } - - return ($force) ? $lastResponse : $this->lastCachedResponse; - } -} diff --git a/lib/Github/HttpClient/HttpClient.php b/lib/Github/HttpClient/HttpClient.php deleted file mode 100644 index 11f65f7bbc2..00000000000 --- a/lib/Github/HttpClient/HttpClient.php +++ /dev/null @@ -1,195 +0,0 @@ - - */ -class HttpClient implements HttpClientInterface -{ - /** - * @var ClientInterface - */ - public $client; - - protected $options = array( - 'base_url' => 'https://api.github.com/', - - 'user_agent' => 'php-github-api (http://github.com/KnpLabs/php-github-api)', - 'timeout' => 10, - - 'api_limit' => 5000, - 'api_version' => 'v3', - - 'cache_dir' => null - ); - - protected $headers = array(); - - private $lastResponse; - private $lastRequest; - - /** - * @param array $options - * @param ClientInterface $client - */ - public function __construct(array $options = array(), ClientInterface $client = null) - { - $this->options = array_merge($this->options, $options); - $client = $client ?: new GuzzleClient($this->options['base_url'], $this->options); - $this->client = $client; - - $this->addListener('request.error', array(new ErrorListener($this->options), 'onRequestError')); - $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( - 'Accept' => sprintf('application/vnd.github.%s+json', $this->options['api_version']), - 'User-Agent' => sprintf('%s', $this->options['user_agent']), - ); - } - - public function addListener($eventName, $listener) - { - $this->client->getEventDispatcher()->addListener($eventName, $listener); - } - - public function addSubscriber(EventSubscriberInterface $subscriber) - { - $this->client->addSubscriber($subscriber); - } - - /** - * {@inheritDoc} - */ - public function get($path, array $parameters = array(), array $headers = array()) - { - return $this->request($path, null, 'GET', $headers, array('query' => $parameters)); - } - - /** - * {@inheritDoc} - */ - public function post($path, $body = null, array $headers = array()) - { - return $this->request($path, $body, 'POST', $headers); - } - - /** - * {@inheritDoc} - */ - public function patch($path, $body = null, array $headers = array()) - { - return $this->request($path, $body, 'PATCH', $headers); - } - - /** - * {@inheritDoc} - */ - public function delete($path, $body = null, array $headers = array()) - { - return $this->request($path, $body, 'DELETE', $headers); - } - - /** - * {@inheritDoc} - */ - public function put($path, $body, array $headers = array()) - { - return $this->request($path, $body, 'PUT', $headers); - } - - /** - * {@inheritDoc} - */ - public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) - { - $request = $this->createRequest($httpMethod, $path, $body, $headers, $options); - - try { - $response = $this->client->send($request); - } catch (\LogicException $e) { - throw new ErrorException($e->getMessage(), $e->getCode(), $e); - } catch (TwoFactorAuthenticationRequiredException $e) { - throw $e; - } catch (\RuntimeException $e) { - throw new RuntimeException($e->getMessage(), $e->getCode(), $e); - } - - $this->lastRequest = $request; - $this->lastResponse = $response; - - return $response; - } - - /** - * {@inheritDoc} - */ - public function authenticate($tokenOrLogin, $password = null, $method) - { - $this->addListener('request.before_send', array( - new AuthListener($tokenOrLogin, $password, $method), 'onRequestBeforeSend' - )); - } - - /** - * @return Request - */ - public function getLastRequest() - { - return $this->lastRequest; - } - - /** - * @return Response - */ - public function getLastResponse() - { - return $this->lastResponse; - } - - protected function createRequest($httpMethod, $path, $body = null, array $headers = array(), array $options = array()) - { - return $this->client->createRequest( - $httpMethod, - $path, - array_merge($this->headers, $headers), - $body, - $options - ); - } -} diff --git a/lib/Github/HttpClient/HttpClientInterface.php b/lib/Github/HttpClient/HttpClientInterface.php deleted file mode 100644 index 5ed0a9e34e5..00000000000 --- a/lib/Github/HttpClient/HttpClientInterface.php +++ /dev/null @@ -1,112 +0,0 @@ - - */ -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 Response - */ - public function get($path, array $parameters = array(), array $headers = array()); - - /** - * Send a POST request. - * - * @param string $path Request path - * @param mixed $body Request body - * @param array $headers Reconfigure the request headers for this call only - * - * @return Response - */ - public function post($path, $body = null, array $headers = array()); - - /** - * Send a PATCH request. - * - * @param string $path Request path - * @param mixed $body Request body - * @param array $headers Reconfigure the request headers for this call only - * - * @internal param array $parameters Request body - * - * @return Response - */ - public function patch($path, $body = null, array $headers = array()); - - /** - * Send a PUT request. - * - * @param string $path Request path - * @param mixed $body Request body - * @param array $headers Reconfigure the request headers for this call only - * - * @return Response - */ - public function put($path, $body, array $headers = array()); - - /** - * Send a DELETE request. - * - * @param string $path Request path - * @param mixed $body Request body - * @param array $headers Reconfigure the request headers for this call only - * - * @return Response - */ - public function delete($path, $body = null, array $headers = array()); - - /** - * Send a request to the server, receive a response, - * decode the response and returns an associative array. - * - * @param string $path Request path - * @param mixed $body Request body - * @param string $httpMethod HTTP method to use - * @param array $headers Request headers - * - * @return Response - */ - public function request($path, $body, $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); - - /** - * Authenticate a user for all next requests. - * - * @param string $tokenOrLogin GitHub private token/username/client ID - * @param null|string $password GitHub password/secret (optionally can contain $authMethod) - * @param null|string $authMethod One of the AUTH_* class constants - * - * @throws InvalidArgumentException If no authentication method was given - */ - public function authenticate($tokenOrLogin, $password, $authMethod); -} diff --git a/lib/Github/HttpClient/Listener/AuthListener.php b/lib/Github/HttpClient/Listener/AuthListener.php deleted file mode 100644 index 41ad42e4642..00000000000 --- a/lib/Github/HttpClient/Listener/AuthListener.php +++ /dev/null @@ -1,68 +0,0 @@ -tokenOrLogin = $tokenOrLogin; - $this->password = $password; - $this->method = $method; - } - - public function onRequestBeforeSend(Event $event) - { - // Skip by default - if (null === $this->method) { - return; - } - - switch ($this->method) { - case Client::AUTH_HTTP_PASSWORD: - $event['request']->setHeader( - 'Authorization', - sprintf('Basic %s', base64_encode($this->tokenOrLogin . ':' . $this->password)) - ); - break; - - case Client::AUTH_HTTP_TOKEN: - $event['request']->setHeader('Authorization', sprintf('token %s', $this->tokenOrLogin)); - break; - - case Client::AUTH_URL_CLIENT_ID: - $url = $event['request']->getUrl(); - - $parameters = array( - 'client_id' => $this->tokenOrLogin, - 'client_secret' => $this->password, - ); - - $url .= (false === strpos($url, '?') ? '?' : '&'); - $url .= utf8_encode(http_build_query($parameters, '', '&')); - - $event['request']->setUrl($url); - break; - - case Client::AUTH_URL_TOKEN: - $url = $event['request']->getUrl(); - $url .= (false === strpos($url, '?') ? '?' : '&'); - $url .= utf8_encode(http_build_query(array('access_token' => $this->tokenOrLogin), '', '&')); - - $event['request']->setUrl($url); - break; - - default: - throw new RuntimeException(sprintf('%s not yet implemented', $this->method)); - break; - } - } -} diff --git a/lib/Github/HttpClient/Message/ResponseMediator.php b/lib/Github/HttpClient/Message/ResponseMediator.php index 5394d80cccb..c841d21269d 100644 --- a/lib/Github/HttpClient/Message/ResponseMediator.php +++ b/lib/Github/HttpClient/Message/ResponseMediator.php @@ -2,15 +2,20 @@ namespace Github\HttpClient\Message; -use Guzzle\Http\Message\Response; use Github\Exception\ApiLimitExceedException; +use Psr\Http\Message\ResponseInterface; class ResponseMediator { - public static function getContent(Response $response) + /** + * @param ResponseInterface $response + * + * @return array|string + */ + public static function getContent(ResponseInterface $response) { - $body = $response->getBody(true); - if (strpos($response->getContentType(), 'application/json') === 0) { + $body = $response->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; @@ -20,14 +25,18 @@ public static function getContent(Response $response) return $body; } - public static function getPagination(Response $response) + /** + * @param ResponseInterface $response + * + * @return array|null + */ + public static function getPagination(ResponseInterface $response) { - $header = (string) $response->getHeader('Link'); - - if (empty($header)) { + 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); @@ -40,9 +49,14 @@ public static function getPagination(Response $response) return $pagination; } - public static function getApiLimit(Response $response) + /** + * @param ResponseInterface $response + * + * @return null|string + */ + public static function getApiLimit(ResponseInterface $response) { - $remainingCalls = (string) $response->getHeader('X-RateLimit-Remaining'); + $remainingCalls = self::getHeader($response, 'X-RateLimit-Remaining'); if (null !== $remainingCalls && 1 > $remainingCalls) { throw new ApiLimitExceedException($remainingCalls); @@ -50,4 +64,18 @@ public static function getApiLimit(Response $response) return $remainingCalls; } + + /** + * Get the value for a single header + * @param ResponseInterface $response + * @param string $name + * + * @return string|null + */ + public static function getHeader(ResponseInterface $response, $name) + { + $headers = $response->getHeader($name); + + return array_shift($headers); + } } diff --git a/lib/Github/HttpClient/Plugin/Authentication.php b/lib/Github/HttpClient/Plugin/Authentication.php new file mode 100644 index 00000000000..4651e6bf302 --- /dev/null +++ b/lib/Github/HttpClient/Plugin/Authentication.php @@ -0,0 +1,81 @@ + + */ +class Authentication implements Plugin +{ + private $tokenOrLogin; + private $password; + private $method; + + public function __construct($tokenOrLogin, $password = null, $method) + { + $this->tokenOrLogin = $tokenOrLogin; + $this->password = $password; + $this->method = $method; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + switch ($this->method) { + case Client::AUTH_HTTP_PASSWORD: + $request = $request->withHeader( + 'Authorization', + sprintf('Basic %s', base64_encode($this->tokenOrLogin.':'.$this->password)) + ); + break; + + case Client::AUTH_HTTP_TOKEN: + $request = $request->withHeader('Authorization', sprintf('token %s', $this->tokenOrLogin)); + break; + + case Client::AUTH_URL_CLIENT_ID: + $uri = $request->getUri(); + $query = $uri->getQuery(); + + $parameters = array( + 'client_id' => $this->tokenOrLogin, + 'client_secret' => $this->password, + ); + + $query .= empty($query) ? '' : '&'; + $query .= utf8_encode(http_build_query($parameters, '', '&')); + + $uri = $uri->withQuery($query); + $request = $request->withUri($uri); + break; + + case Client::AUTH_URL_TOKEN: + $uri = $request->getUri(); + $query = $uri->getQuery(); + + $parameters = array('access_token' => $this->tokenOrLogin); + + $query .= empty($query) ? '' : '&'; + $query .= utf8_encode(http_build_query($parameters, '', '&')); + + $uri = $uri->withQuery($query); + $request = $request->withUri($uri); + break; + + default: + throw new RuntimeException(sprintf('%s not yet implemented', $this->method)); + break; + } + + return $next($request); + } +} diff --git a/lib/Github/HttpClient/Plugin/Cache.php b/lib/Github/HttpClient/Plugin/Cache.php new file mode 100644 index 00000000000..cc4397dd59e --- /dev/null +++ b/lib/Github/HttpClient/Plugin/Cache.php @@ -0,0 +1,87 @@ + + * @author Tobias Nyholm + */ +class Cache implements Plugin +{ + /** + * @var CacheInterface + */ + protected $cache; + + /** + * + * @param CacheInterface $cache + */ + public function __construct(CacheInterface $cache = null) + { + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $cacheKey = sha1($request->getUri()->__toString()); + + if ($modifiedAt = $this->getCache()->getModifiedSince($cacheKey)) { + $modifiedAt = new \DateTime('@'.$modifiedAt); + $modifiedAt->setTimezone(new \DateTimeZone('GMT')); + + $request = $request->withHeader( + 'If-Modified-Since', + sprintf('%s GMT', $modifiedAt->format('l, d-M-y H:i:s')) + ); + } + if ($etag = $this->getCache()->getETag($cacheKey)) { + $request = $request->withHeader( + 'If-None-Match', + $etag + ); + } + + return $next($request)->then(function (ResponseInterface $response) use ($request, $cacheKey) { + if (304 === $response->getStatusCode()) { + $cacheResponse = $this->getCache()->get($cacheKey); + $this->lastCachedResponse = $cacheResponse; + + return $cacheResponse; + } + + if (in_array($request->getMethod(), array('GET', 'HEAD'), true)) { + $this->getCache()->set($cacheKey, $response); + } + + return $response; + }); + } + + + /** + * @return CacheInterface + */ + public function getCache() + { + if (null === $this->cache) { + $this->cache = new FilesystemCache(sys_get_temp_dir().DIRECTORY_SEPARATOR.'php-github-api-cache'); + } + + return $this->cache; + } +} diff --git a/lib/Github/HttpClient/Listener/ErrorListener.php b/lib/Github/HttpClient/Plugin/GithubExceptionThrower.php similarity index 72% rename from lib/Github/HttpClient/Listener/ErrorListener.php rename to lib/Github/HttpClient/Plugin/GithubExceptionThrower.php index 3772fbbb27a..d3042bc6dc0 100644 --- a/lib/Github/HttpClient/Listener/ErrorListener.php +++ b/lib/Github/HttpClient/Plugin/GithubExceptionThrower.php @@ -1,53 +1,44 @@ + * @author Tobias Nyholm */ -class ErrorListener +class GithubExceptionThrower implements Plugin { /** - * @var array + * {@inheritdoc} */ - private $options; - - /** - * @param array $options - */ - public function __construct(array $options) + public function handleRequest(RequestInterface $request, callable $next, callable $first) { - $this->options = $options; - } - - /** - * {@inheritDoc} - */ - public function onRequestError(Event $event) - { - /** @var $request \Guzzle\Http\Message\Request */ - $request = $event['request']; - $response = $request->getResponse(); + return $next($request)->then(function (ResponseInterface $response) use ($request) { + if ($response->getStatusCode() < 400 || $response->getStatusCode() > 600) { + return $response; + } - if ($response->isClientError() || $response->isServerError()) { - $remaining = (string) $response->getHeader('X-RateLimit-Remaining'); - $limit = $response->getHeader('X-RateLimit-Limit'); + // If error: + $remaining = ResponseMediator::getHeader($response, 'X-RateLimit-Remaining'); + $limit = ResponseMediator::getHeader($response, 'X-RateLimit-Limit'); - if (null != $remaining && 1 > $remaining && 'rate_limit' !== substr($request->getResource(), 1, 10)) { + if (null != $remaining && 1 > $remaining && 'rate_limit' !== substr($request->getRequestTarget(), 1, 10)) { throw new ApiLimitExceedException($limit); } if (401 === $response->getStatusCode()) { - if ($response->hasHeader('X-GitHub-OTP') && 0 === strpos((string) $response->getHeader('X-GitHub-OTP'), 'required;')) { - $type = substr((string) $response->getHeader('X-GitHub-OTP'), 9); + if ($response->hasHeader('X-GitHub-OTP') && 0 === strpos((string) ResponseMediator::getHeader($response, 'X-GitHub-OTP'), 'required;')) { + $type = substr((string) ResponseMediator::getHeader($response, 'X-GitHub-OTP'), 9); throw new TwoFactorAuthenticationRequiredException($type); } @@ -88,11 +79,11 @@ public function onRequestError(Event $event) } } - throw new ValidationFailedException('Validation Failed: ' . implode(', ', $errors), 422); + throw new ValidationFailedException('Validation Failed: '.implode(', ', $errors), 422); } } throw new RuntimeException(isset($content['message']) ? $content['message'] : $content, $response->getStatusCode()); - }; + }); } } diff --git a/lib/Github/HttpClient/Plugin/History.php b/lib/Github/HttpClient/Plugin/History.php new file mode 100644 index 00000000000..303d81404dc --- /dev/null +++ b/lib/Github/HttpClient/Plugin/History.php @@ -0,0 +1,38 @@ + + */ +class History implements Journal +{ + /** + * @var ResponseInterface + */ + private $lastResponse; + + /** + * @return ResponseInterface|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + public function addSuccess(RequestInterface $request, ResponseInterface $response) + { + $this->lastResponse = $response; + } + + public function addFailure(RequestInterface $request, Exception $exception) + { + } +} diff --git a/lib/Github/HttpClient/Plugin/PathPrepend.php b/lib/Github/HttpClient/Plugin/PathPrepend.php new file mode 100644 index 00000000000..b3b840e1487 --- /dev/null +++ b/lib/Github/HttpClient/Plugin/PathPrepend.php @@ -0,0 +1,37 @@ + + */ +class PathPrepend implements Plugin +{ + private $path; + + /** + * @param string $path + */ + public function __construct($path) + { + $this->path = $path; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(RequestInterface $request, callable $next, callable $first) + { + $currentPath = $request->getUri()->getPath(); + $uri = $request->getUri()->withPath($this->path.$currentPath); + + $request = $request->withUri($uri); + + return $next($request); + } +} diff --git a/lib/Github/ResultPager.php b/lib/Github/ResultPager.php index 21bc08ec3f4..b63ffd676ec 100644 --- a/lib/Github/ResultPager.php +++ b/lib/Github/ResultPager.php @@ -59,7 +59,7 @@ public function getPagination() */ public function fetch(ApiInterface $api, $method, array $parameters = array()) { - $result = call_user_func_array(array($api, $method), $parameters); + $result = $this->callApi($api, $method, $parameters); $this->postFetch(); return $result; @@ -78,8 +78,7 @@ public function fetchAll(ApiInterface $api, $method, array $parameters = array() // set parameters per_page to GitHub max to minimize number of requests $api->setPerPage(100); - $result = array(); - $result = call_user_func_array(array($api, $method), $parameters); + $result = $this->callApi($api, $method, $parameters); $this->postFetch(); if ($isSearch) { @@ -107,7 +106,7 @@ public function fetchAll(ApiInterface $api, $method, array $parameters = array() */ public function postFetch() { - $this->pagination = ResponseMediator::getPagination($this->client->getHttpClient()->getLastResponse()); + $this->pagination = ResponseMediator::getPagination($this->client->getLastResponse()); } /** @@ -178,4 +177,16 @@ protected function get($key) return ResponseMediator::getContent($result); } } + + /** + * @param ApiInterface $api + * @param $method + * @param array $parameters + * + * @return mixed + */ + protected function callApi(ApiInterface $api, $method, array $parameters) + { + return call_user_func_array(array($api, $method), $parameters); + } } diff --git a/test/Github/Tests/Api/AbstractApiTest.php b/test/Github/Tests/Api/AbstractApiTest.php index f3982e398e6..0edd5daf7b7 100644 --- a/test/Github/Tests/Api/AbstractApiTest.php +++ b/test/Github/Tests/Api/AbstractApiTest.php @@ -3,7 +3,7 @@ namespace Github\Tests\Api; use Github\Api\AbstractApi; -use Guzzle\Http\Message\Response; +use GuzzleHttp\Psr7\Response; class AbstractApiTest extends \PHPUnit_Framework_TestCase { @@ -14,15 +14,17 @@ public function shouldPassGETRequestToClient() { $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('get')); $httpClient ->expects($this->any()) ->method('get') - ->with('/path', array('param1' => 'param1value'), array('header1' => 'header1value')) - ->will($this->returnValue($expectedArray)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); - + ->with('/path?param1=param1value', array('header1' => 'header1value')) + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); + $api = $this->getAbstractApiObject($client); $this->assertEquals($expectedArray, $api->get('/path', array('param1' => 'param1value'), array('header1' => 'header1value'))); @@ -35,14 +37,17 @@ public function shouldPassPOSTRequestToClient() { $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('post')); $httpClient ->expects($this->once()) ->method('post') - ->with('/path', array('param1' => 'param1value'), array('option1' => 'option1value')) - ->will($this->returnValue($expectedArray)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); + ->with('/path', array('option1' => 'option1value'), json_encode(array('param1' => 'param1value'))) + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); $api = $this->getAbstractApiObject($client); @@ -56,14 +61,17 @@ public function shouldPassPATCHRequestToClient() { $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('patch')); $httpClient ->expects($this->once()) ->method('patch') - ->with('/path', array('param1' => 'param1value'), array('option1' => 'option1value')) - ->will($this->returnValue($expectedArray)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); + ->with('/path', array('option1' => 'option1value'), json_encode(array('param1' => 'param1value'))) + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); $api = $this->getAbstractApiObject($client); @@ -77,14 +85,17 @@ public function shouldPassPUTRequestToClient() { $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('put')); $httpClient ->expects($this->once()) ->method('put') - ->with('/path', array('param1' => 'param1value'), array('option1' => 'option1value')) - ->will($this->returnValue($expectedArray)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); + ->with('/path', array('option1' => 'option1value'), json_encode(array('param1' => 'param1value'))) + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); $api = $this->getAbstractApiObject($client); @@ -98,14 +109,18 @@ public function shouldPassDELETERequestToClient() { $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('delete')); $httpClient ->expects($this->once()) ->method('delete') - ->with('/path', array('param1' => 'param1value'), array('option1' => 'option1value')) - ->will($this->returnValue($expectedArray)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); + ->with('/path', array('option1' => 'option1value'), json_encode(array('param1' => 'param1value'))) + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); + $api = $this->getAbstractApiObject($client); @@ -117,18 +132,21 @@ public function shouldPassDELETERequestToClient() */ public function shouldNotPassEmptyRefToClient() { - $expectedResponse = new Response('value'); + $expectedArray = array('value'); - $httpClient = $this->getHttpMock(); + $httpClient = $this->getHttpMethodsMock(array('get')); $httpClient ->expects($this->any()) ->method('get') ->with('/path', array()) - ->will($this->returnValue($expectedResponse)); - $client = $this->getClientMock(); - $client->setHttpClient($httpClient); + ->will($this->returnValue($this->getPSR7Response($expectedArray))); + + $client = $this->getMock('Github\Client', array('getHttpClient')); + $client->expects($this->any()) + ->method('getHttpClient') + ->willReturn($httpClient); - $api = new ExposedAbstractApiTestInstance($client); + $api = $this->getAbstractApiObject($client); $api->get('/path', array('ref' => null)); } @@ -142,26 +160,50 @@ protected function getAbstractApiObject($client) */ protected function getClientMock() { - return new \Github\Client($this->getHttpMock()); + return new \Github\Client($this->getHttpMethodsMock()); } /** - * @return \Github\HttpClient\HttpClientInterface + * Return a HttpMethods client mock + * + * @return \Http\Client\Common\HttpMethodsClient */ - protected function getHttpMock() + protected function getHttpMethodsMock(array $methods = array()) { - return $this->getMock('Github\HttpClient\HttpClient', array(), array(array(), $this->getHttpClientMock())); - } + $methods = array_merge(array('sendRequest'), $methods); + $mock = $this->getMock('Http\Client\Common\HttpMethodsClient', $methods, array(), '', false); + $mock + ->expects($this->any()) + ->method('sendRequest'); + return $mock; + } + /** + * @return \Http\Client\HttpClient + */ protected function getHttpClientMock() { - $mock = $this->getMock('Guzzle\Http\Client', array('send')); + $mock = $this->getMock('Http\Client\HttpClient', array('sendRequest')); $mock ->expects($this->any()) - ->method('send'); + ->method('sendRequest'); return $mock; } + + /** + * @param $expectedArray + * + * @return Response + */ + private function getPSR7Response($expectedArray) + { + return new Response( + 200, + array('Content-Type' => 'application/json'), + \GuzzleHttp\Psr7\stream_for(json_encode($expectedArray)) + ); + } } class AbstractApiTestInstance extends AbstractApi @@ -171,7 +213,7 @@ class AbstractApiTestInstance extends AbstractApi */ public function get($path, array $parameters = array(), $requestHeaders = array()) { - return $this->client->getHttpClient()->get($path, $parameters, $requestHeaders); + return parent::get($path, $parameters, $requestHeaders); } /** @@ -179,7 +221,7 @@ public function get($path, array $parameters = array(), $requestHeaders = array( */ public function post($path, array $parameters = array(), $requestHeaders = array()) { - return $this->client->getHttpClient()->post($path, $parameters, $requestHeaders); + return parent::post($path, $parameters, $requestHeaders); } /** @@ -187,7 +229,7 @@ public function post($path, array $parameters = array(), $requestHeaders = array */ public function postRaw($path, $body, $requestHeaders = array()) { - return $this->client->getHttpClient()->post($path, $body, $requestHeaders); + return parent::postRaw($path, $body, $requestHeaders); } /** @@ -195,7 +237,7 @@ public function postRaw($path, $body, $requestHeaders = array()) */ public function patch($path, array $parameters = array(), $requestHeaders = array()) { - return $this->client->getHttpClient()->patch($path, $parameters, $requestHeaders); + return parent::patch($path, $parameters, $requestHeaders); } /** @@ -203,7 +245,7 @@ public function patch($path, array $parameters = array(), $requestHeaders = arra */ public function put($path, array $parameters = array(), $requestHeaders = array()) { - return $this->client->getHttpClient()->put($path, $parameters, $requestHeaders); + return parent::put($path, $parameters, $requestHeaders); } /** @@ -211,10 +253,13 @@ public function put($path, array $parameters = array(), $requestHeaders = array( */ public function delete($path, array $parameters = array(), $requestHeaders = array()) { - return $this->client->getHttpClient()->delete($path, $parameters, $requestHeaders); + return parent::delete($path, $parameters, $requestHeaders); } } +/** + * @deprecated + */ class ExposedAbstractApiTestInstance extends AbstractApi { /** diff --git a/test/Github/Tests/Api/Repository/AssetsTest.php b/test/Github/Tests/Api/Repository/AssetsTest.php index dc6e6146b0b..174268468ca 100644 --- a/test/Github/Tests/Api/Repository/AssetsTest.php +++ b/test/Github/Tests/Api/Repository/AssetsTest.php @@ -60,7 +60,7 @@ public function shouldCreateReleaseAsset() $api = $this->getApiMock(); $api->expects($this->once()) ->method('postRaw') - ->with('repos/KnpLabs/php-github-api/releases/'. $releaseId .'/assets?name='.$name) + ->with('https://uploads.github.com/repos/KnpLabs/php-github-api/releases/'. $releaseId .'/assets?name='.$name) ->will($this->returnValue($body)); $this->assertEquals($body, $api->create('KnpLabs', 'php-github-api', $releaseId, $name, $contentType, $body)); diff --git a/test/Github/Tests/Api/Repository/ContentsTest.php b/test/Github/Tests/Api/Repository/ContentsTest.php index ddce2c9e905..cc9bb693940 100644 --- a/test/Github/Tests/Api/Repository/ContentsTest.php +++ b/test/Github/Tests/Api/Repository/ContentsTest.php @@ -4,6 +4,7 @@ use Github\Tests\Api\TestCase; use Github\Exception\TwoFactorAuthenticationRequiredException; +use GuzzleHttp\Psr7\Response; class ContentsTest extends TestCase { @@ -44,34 +45,24 @@ public function shouldShowReadme() */ public function shouldReturnTrueWhenFileExists() { - $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') - ->disableOriginalConstructor() - ->getMock(); - - $responseMock->expects($this->any()) - ->method('getStatusCode') - ->willReturn(200); + $response = new Response(200); $api = $this->getApiMock(); $api->expects($this->once()) ->method('head') ->with('repos/KnpLabs/php-github-api/contents/composer.json', array('ref' => null)) - ->will($this->returnValue($responseMock)); + ->will($this->returnValue($response)); $this->assertEquals(true, $api->exists('KnpLabs', 'php-github-api', 'composer.json')); } public function getFailureStubsForExistsTest() { - $nonOkResponseMock =$this->getGuzzleResponseMock(); - - $nonOkResponseMock->expects($this->any()) - ->method('getStatusCode') - ->willReturn(403); + $response = new Response(403); return array( array($this->throwException(new \ErrorException())), - array($this->returnValue($nonOkResponseMock)) + array($this->returnValue($response)) ); } @@ -331,13 +322,4 @@ protected function getApiClass() { return 'Github\Api\Repository\Contents'; } - - private function getGuzzleResponseMock() - { - $responseMock = $this->getMockBuilder('\Guzzle\Http\Message\Response') - ->disableOriginalConstructor() - ->getMock(); - - return $responseMock; - } } diff --git a/test/Github/Tests/Api/TestCase.php b/test/Github/Tests/Api/TestCase.php index a8f322e31e7..965781cd9d7 100644 --- a/test/Github/Tests/Api/TestCase.php +++ b/test/Github/Tests/Api/TestCase.php @@ -8,15 +8,12 @@ abstract protected function getApiClass(); protected function getApiMock() { - $httpClient = $this->getMock('Guzzle\Http\Client', array('send')); + $httpClient = $this->getMock('Http\Client\HttpClient', array('sendRequest')); $httpClient ->expects($this->any()) - ->method('send'); + ->method('sendRequest'); - $mock = $this->getMock('Github\HttpClient\HttpClient', array(), array(array(), $httpClient)); - - $client = new \Github\Client($mock); - $client->setHttpClient($mock); + $client = new \Github\Client($httpClient); return $this->getMockBuilder($this->getApiClass()) ->setMethods(array('get', 'post', 'postRaw', 'patch', 'delete', 'put', 'head')) diff --git a/test/Github/Tests/ClientTest.php b/test/Github/Tests/ClientTest.php index a51996319d7..0a9983eb20b 100644 --- a/test/Github/Tests/ClientTest.php +++ b/test/Github/Tests/ClientTest.php @@ -4,6 +4,8 @@ use Github\Client; use Github\Exception\BadMethodCallException; +use Github\HttpClient\Plugin\Authentication; +use Http\Client\Common\Plugin; class ClientTest extends \PHPUnit_Framework_TestCase { @@ -14,7 +16,7 @@ public function shouldNotHaveToPassHttpClientToConstructor() { $client = new Client(); - $this->assertInstanceOf('Github\HttpClient\HttpClient', $client->getHttpClient()); + $this->assertInstanceOf('\Http\Client\HttpClient', $client->getHttpClient()); } /** @@ -22,9 +24,9 @@ public function shouldNotHaveToPassHttpClientToConstructor() */ public function shouldPassHttpClientInterfaceToConstructor() { - $client = new Client($this->getHttpClientMock()); + $client = new Client($this->getMock('Http\Client\HttpClient')); - $this->assertInstanceOf('Github\HttpClient\HttpClientInterface', $client->getHttpClient()); + $this->assertInstanceOf('Http\Client\HttpClient', $client->getHttpClient()); } /** @@ -33,12 +35,15 @@ public function shouldPassHttpClientInterfaceToConstructor() */ public function shouldAuthenticateUsingAllGivenParameters($login, $password, $method) { - $httpClient = $this->getHttpClientMock(); - $httpClient->expects($this->once()) - ->method('authenticate') - ->with($login, $password, $method); + $client = $this->getMock('Github\Client', array('addPlugin', 'removePlugin')); + $client->expects($this->once()) + ->method('addPlugin') + ->with($this->equalTo(new Authentication($login, $password, $method))); + + $client->expects($this->once()) + ->method('removePlugin') + ->with(Authentication::class); - $client = new Client($httpClient); $client->authenticate($login, $password, $method); } @@ -58,12 +63,15 @@ public function getAuthenticationFullData() */ public function shouldAuthenticateUsingGivenParameters($token, $method) { - $httpClient = $this->getHttpClientMock(); - $httpClient->expects($this->once()) - ->method('authenticate') - ->with($token, null, $method); + $client = $this->getMock('Github\Client', array('addPlugin', 'removePlugin')); + $client->expects($this->once()) + ->method('addPlugin') + ->with($this->equalTo(new Authentication($token, null, $method))); + + $client->expects($this->once()) + ->method('removePlugin') + ->with(Authentication::class); - $client = new Client($httpClient); $client->authenticate($token, $method); } @@ -81,36 +89,46 @@ public function getAuthenticationPartialData() */ public function shouldThrowExceptionWhenAuthenticatingWithoutMethodSet() { - $httpClient = $this->getHttpClientMock(array('addListener')); + $client = new Client(); - $client = new Client($httpClient); $client->authenticate('login', null, null); } /** * @test */ - public function shouldClearHeadersLazy() + public function shouldClearHeaders() { - $httpClient = $this->getHttpClientMock(array('clearHeaders')); - $httpClient->expects($this->once())->method('clearHeaders'); + $client = $this->getMock('Github\Client', array('addPlugin', 'removePlugin')); + $client->expects($this->once()) + ->method('addPlugin') + ->with($this->isInstanceOf(Plugin\HeaderAppendPlugin::class)); + + $client->expects($this->once()) + ->method('removePlugin') + ->with(Plugin\HeaderAppendPlugin::class); - $client = new Client($httpClient); $client->clearHeaders(); } /** * @test */ - public function shouldSetHeadersLaizly() + public function shouldAddHeaders() { $headers = array('header1', 'header2'); - $httpClient = $this->getHttpClientMock(); - $httpClient->expects($this->once())->method('setHeaders')->with($headers); + $client = $this->getMock('Github\Client', array('addPlugin', 'removePlugin')); + $client->expects($this->once()) + ->method('addPlugin') + // TODO verify that headers exists + ->with($this->isInstanceOf(Plugin\HeaderAppendPlugin::class)); + + $client->expects($this->once()) + ->method('removePlugin') + ->with(Plugin\HeaderAppendPlugin::class); - $client = new Client($httpClient); - $client->setHeaders($headers); + $client->addHeaders($headers); } /** @@ -199,14 +217,4 @@ public function getApiClassesProvider() array('meta', 'Github\Api\Meta') ); } - - public function getHttpClientMock(array $methods = array()) - { - $methods = array_merge( - array('get', 'post', 'patch', 'put', 'delete', 'request', 'setOption', 'setHeaders', 'authenticate'), - $methods - ); - - return $this->getMock('Github\HttpClient\HttpClientInterface', $methods); - } } diff --git a/test/Github/Tests/Functional/MarkdownTest.php b/test/Github/Tests/Functional/MarkdownTest.php index 0e7e2b033ba..34c967cbbef 100644 --- a/test/Github/Tests/Functional/MarkdownTest.php +++ b/test/Github/Tests/Functional/MarkdownTest.php @@ -2,6 +2,8 @@ namespace Github\Tests\Functional; +use Github\Api\Markdown; + /** * @group functional */ @@ -12,6 +14,7 @@ class MarkdownTest extends TestCase */ public function shouldRetrieveParsedMarkdownContent() { + /** @var Markdown $api */ $api = $this->client->api('markdown'); $input = 'Hello world github/linguist#1 **cool**, and #1!'; diff --git a/test/Github/Tests/Functional/RepoTest.php b/test/Github/Tests/Functional/RepoTest.php index b45c27c832a..7416a3384d8 100644 --- a/test/Github/Tests/Functional/RepoTest.php +++ b/test/Github/Tests/Functional/RepoTest.php @@ -12,7 +12,7 @@ class RepoTest extends TestCase */ public function shouldShowPRDiffIfHeaderIsPresent() { - $this->client->setHeaders( + $this->client->addHeaders( array('Accept' => sprintf( 'application/vnd.github.%s.diff', $this->client->getOption('api_version') diff --git a/test/Github/Tests/Functional/TestCase.php b/test/Github/Tests/Functional/TestCase.php index ef51c1a52c9..17c76cdc15e 100644 --- a/test/Github/Tests/Functional/TestCase.php +++ b/test/Github/Tests/Functional/TestCase.php @@ -11,6 +11,9 @@ */ class TestCase extends \PHPUnit_Framework_TestCase { + /** + * @var Client + */ protected $client; public function setUp() diff --git a/test/Github/Tests/HttpClient/Cache/FilesystemCacheTest.php b/test/Github/Tests/HttpClient/Cache/FilesystemCacheTest.php index 54efdd49ba4..6383fecad5a 100644 --- a/test/Github/Tests/HttpClient/Cache/FilesystemCacheTest.php +++ b/test/Github/Tests/HttpClient/Cache/FilesystemCacheTest.php @@ -2,8 +2,8 @@ namespace Github\Tests\HttpClient\Cache; -use Guzzle\Http\Message\Response; use Github\HttpClient\Cache\FilesystemCache; +use GuzzleHttp\Psr7\Response; class FilesystemCacheTest extends \PHPUnit_Framework_TestCase { diff --git a/test/Github/Tests/HttpClient/Cache/GaufretteCacheTest.php b/test/Github/Tests/HttpClient/Cache/GaufretteCacheTest.php index 2a84f14ad54..2e033f53b46 100644 --- a/test/Github/Tests/HttpClient/Cache/GaufretteCacheTest.php +++ b/test/Github/Tests/HttpClient/Cache/GaufretteCacheTest.php @@ -2,8 +2,8 @@ namespace Github\Tests\HttpClient\Cache; -use Guzzle\Http\Message\Response; use Github\HttpClient\Cache\GaufretteCache; +use GuzzleHttp\Psr7\Response; class GaufretteCacheTest extends \PHPUnit_Framework_TestCase { diff --git a/test/Github/Tests/HttpClient/CachedHttpClientTest.php b/test/Github/Tests/HttpClient/CachedHttpClientTest.php deleted file mode 100644 index 477b3e2eab3..00000000000 --- a/test/Github/Tests/HttpClient/CachedHttpClientTest.php +++ /dev/null @@ -1,78 +0,0 @@ -getCacheMock(); - $response = new Response(200); - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('send') - ->will($this->returnValue($response)); - - $httpClient = new CachedHttpClient(array('base_url' => ''), $client); - $httpClient->setCache($cache); - - $cache->expects($this->once())->method('set')->with('test', $response); - $httpClient->get('test'); - } - - /** - * @test - */ - public function shouldGetCachedResponseWhileResourceNotModified() - { - $cache = $this->getCacheMock(); - $response = new Response(304); - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('send') - ->will($this->returnValue($response)); - - $httpClient = new CachedHttpClient(array('base_url' => ''), $client); - $httpClient->setCache($cache); - $httpClient->fakeResponse = $response; - - $cache->expects($this->once())->method('get')->with('test'); - - $httpClient->get('test'); - } - - /** - * @test - */ - public function shouldRenewCacheWhenResourceHasChanged() - { - $cache = $this->getCacheMock(); - $response = new Response(200); - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('send') - ->will($this->returnValue($response)); - - $httpClient = new CachedHttpClient(array('base_url' => ''), $client); - $httpClient->setCache($cache); - - $cache->expects($this->once())->method('set')->with('test', $response); - $cache->expects($this->once())->method('getModifiedSince')->with('test')->will($this->returnValue(1256953732)); - - $httpClient->get('test'); - } - - public function getCacheMock() - { - return $this->getMock('Github\HttpClient\Cache\CacheInterface'); - } -} diff --git a/test/Github/Tests/HttpClient/HttpClientTest.php b/test/Github/Tests/HttpClient/HttpClientTest.php deleted file mode 100644 index c6a645a214f..00000000000 --- a/test/Github/Tests/HttpClient/HttpClientTest.php +++ /dev/null @@ -1,299 +0,0 @@ - 33 - ), $this->getBrowserMock()); - - $this->assertEquals(33, $httpClient->getOption('timeout')); - $this->assertEquals(5000, $httpClient->getOption('api_limit')); - } - - /** - * @test - */ - public function shouldBeAbleToSetOption() - { - $httpClient = new TestHttpClient(array(), $this->getBrowserMock()); - $httpClient->setOption('timeout', 666); - - $this->assertEquals(666, $httpClient->getOption('timeout')); - } - - /** - * @test - * @dataProvider getAuthenticationFullData - */ - public function shouldAuthenticateUsingAllGivenParameters($login, $password, $method) - { - $client = new GuzzleClient(); - $listeners = $client->getEventDispatcher()->getListeners('request.before_send'); - $this->assertCount(1, $listeners); - - $httpClient = new TestHttpClient(array(), $client); - $httpClient->authenticate($login, $password, $method); - - $listeners = $client->getEventDispatcher()->getListeners('request.before_send'); - $this->assertCount(2, $listeners); - - $authListener = $listeners[1][0]; - $this->assertInstanceOf('Github\HttpClient\Listener\AuthListener', $authListener); - } - - public function getAuthenticationFullData() - { - return array( - array('login', 'password', Client::AUTH_HTTP_PASSWORD), - array('token', null, Client::AUTH_HTTP_TOKEN), - array('token', null, Client::AUTH_URL_TOKEN), - array('client_id', 'client_secret', Client::AUTH_URL_CLIENT_ID), - ); - } - - /** - * @test - */ - public function shouldDoGETRequest() - { - $path = '/some/path'; - $parameters = array('a' => 'b'); - $headers = array('c' => 'd'); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->get($path, $parameters, $headers); - } - - /** - * @test - */ - public function shouldDoPOSTRequest() - { - $path = '/some/path'; - $body = 'a = b'; - $headers = array('c' => 'd'); - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('createRequest') - ->with('POST', $path, $this->isType('array'), $body); - - $httpClient = new HttpClient(array(), $client); - $httpClient->post($path, $body, $headers); - } - - /** - * @test - */ - public function shouldDoPOSTRequestWithoutContent() - { - $path = '/some/path'; - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('createRequest') - ->with('POST', $path, $this->isType('array')); - - $httpClient = new HttpClient(array(), $client); - $httpClient->post($path); - } - - /** - * @test - */ - public function shouldDoPATCHRequest() - { - $path = '/some/path'; - $body = 'a = b'; - $headers = array('c' => 'd'); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->patch($path, $body, $headers); - } - - /** - * @test - */ - public function shouldDoDELETERequest() - { - $path = '/some/path'; - $body = 'a = b'; - $headers = array('c' => 'd'); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->delete($path, $body, $headers); - } - - /** - * @test - */ - public function shouldDoPUTRequest() - { - $path = '/some/path'; - $headers = array('c' => 'd'); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->put($path, $headers); - } - - /** - * @test - */ - public function shouldDoCustomRequest() - { - $path = '/some/path'; - $body = 'a = b'; - $options = array('c' => 'd'); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->request($path, $body, 'HEAD', $options); - } - - /** - * @test - */ - public function shouldHandlePagination() - { - $path = '/some/path'; - $body = 'a = b'; - $headers = array('c' => 'd'); - - $response = new Response(200); - $response->addHeader('Link', "; rel=\"page2\", \n; rel=\"page4\""); - - $client = $this->getBrowserMock(); - - $httpClient = new HttpClient(array(), $client); - $httpClient->request($path, $body, 'HEAD', $headers); - - $this->assertEquals(array('page2' => 'page1', 'page4' => 'page3'), ResponseMediator::getPagination($response)); - } - - /** - * @test - */ - public function shouldAllowToReturnRawContent() - { - $path = '/some/path'; - $parameters = array('a = b'); - $headers = array('c' => 'd'); - - $message = $this->getMock('Guzzle\Http\Message\Response', array(), array(200)); - $message->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('Just raw context')); - - $client = $this->getBrowserMock(); - $client->expects($this->once()) - ->method('send') - ->will($this->returnValue($message)); - - $httpClient = new TestHttpClient(array(), $client); - $response = $httpClient->get($path, $parameters, $headers); - - $this->assertEquals("Just raw context", $response->getBody()); - $this->assertInstanceOf('Guzzle\Http\Message\MessageInterface', $response); - } - - /** - * @test - * @expectedException \Github\Exception\ApiLimitExceedException - */ - public function shouldThrowExceptionWhenApiIsExceeded() - { - $path = '/some/path'; - $parameters = array('a = b'); - $headers = array('c' => 'd'); - - $response = new Response(403); - $response->addHeader('X-RateLimit-Remaining', 0); - - $mockPlugin = new MockPlugin(); - $mockPlugin->addResponse($response); - - $client = new GuzzleClient('http://123.com/'); - $client->addSubscriber($mockPlugin); - - $httpClient = new TestHttpClient(array(), $client); - $httpClient->get($path, $parameters, $headers); - } - - /** - * @test - * @expectedException \Github\Exception\TwoFactorAuthenticationRequiredException - */ - public function shouldForwardTwoFactorAuthenticationExceptionWhenItHappens() - { - $path = '/some/path'; - $parameters = array('a = b'); - $headers = array('c' => 'd'); - - $response = new Response(401); - $response->addHeader('X-GitHub-OTP', 'required; sms'); - - $mockPlugin = new MockPlugin(); - $mockPlugin->addResponse($response); - - $client = new GuzzleClient('http://123.com/'); - $client->addSubscriber($mockPlugin); - - $httpClient = new TestHttpClient(array(), $client); - $httpClient->get($path, $parameters, $headers); - } - - protected function getBrowserMock(array $methods = array()) - { - $mock = $this->getMock( - 'Guzzle\Http\Client', - array_merge( - array('send', 'createRequest'), - $methods - ) - ); - - $mock->expects($this->any()) - ->method('createRequest') - ->will($this->returnValue($this->getMock('Guzzle\Http\Message\Request', array(), array('GET', 'some')))); - - return $mock; - } -} - -class TestHttpClient extends HttpClient -{ - public function getOption($name, $default = null) - { - return isset($this->options[$name]) ? $this->options[$name] : $default; - } - - public function request($path, $body, $httpMethod = 'GET', array $headers = array(), array $options = array()) - { - $request = $this->client->createRequest($httpMethod, $path); - - return $this->client->send($request); - } -} diff --git a/test/Github/Tests/HttpClient/Listener/AuthListenerTest.php b/test/Github/Tests/HttpClient/Listener/AuthListenerTest.php deleted file mode 100644 index 68fd4387401..00000000000 --- a/test/Github/Tests/HttpClient/Listener/AuthListenerTest.php +++ /dev/null @@ -1,137 +0,0 @@ -onRequestBeforeSend($this->getEventMock()); - } - - /** - * @test - */ - public function shouldDoNothingForHaveNullMethod() - { - $request = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $request->expects($this->never()) - ->method('addHeader'); - $request->expects($this->never()) - ->method('fromUrl'); - $request->expects($this->never()) - ->method('getUrl'); - - $listener = new AuthListener('test', 'pass', null); - $listener->onRequestBeforeSend($this->getEventMock($request)); - } - - /** - * @test - */ - public function shouldDoNothingForPostSend() - { - $request = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $request->expects($this->never()) - ->method('addHeader'); - $request->expects($this->never()) - ->method('fromUrl'); - $request->expects($this->never()) - ->method('getUrl'); - - $listener = new AuthListener('login', 'somepassphrase', Client::AUTH_HTTP_PASSWORD); - $listener->onRequestBeforeSend($this->getEventMock($request)); - } - - /** - * @test - */ - public function shouldSetAuthBasicHeaderForAuthPassMethod() - { - $expected = 'Basic '.base64_encode('login2:pass42323'); - - $request = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $request->expects($this->once()) - ->method('setHeader') - ->with('Authorization', $expected); - $request->expects($this->once()) - ->method('getHeader') - ->with('Authorization') - ->will($this->returnValue($expected)); - - $listener = new AuthListener('login2', 'pass42323', Client::AUTH_HTTP_PASSWORD); - $listener->onRequestBeforeSend($this->getEventMock($request)); - - $this->assertEquals($expected, $request->getHeader('Authorization')); - } - - /** - * @test - */ - public function shouldSetAuthTokenHeaderForAuthPassMethod() - { - $expected = 'token test'; - - $request = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $request->expects($this->once()) - ->method('setHeader') - ->with('Authorization', $expected); - $request->expects($this->once()) - ->method('getHeader') - ->with('Authorization') - ->will($this->returnValue($expected)); - - $listener = new AuthListener('test', null, Client::AUTH_HTTP_TOKEN); - $listener->onRequestBeforeSend($this->getEventMock($request)); - - $this->assertEquals($expected, $request->getHeader('Authorization')); - } - - /** - * @test - */ - public function shouldSetTokenInUrlForAuthUrlMethod() - { - $request = new Request('GET', '/res'); - - $listener = new AuthListener('test', null, Client::AUTH_URL_TOKEN); - $listener->onRequestBeforeSend($this->getEventMock($request)); - - $this->assertEquals('/res?access_token=test', $request->getUrl()); - } - - /** - * @test - */ - public function shouldSetClientDetailsInUrlForAuthUrlMethod() - { - $request = new Request('GET', '/res'); - - $listener = new AuthListener('clientId', 'clientSecret', Client::AUTH_URL_CLIENT_ID); - $listener->onRequestBeforeSend($this->getEventMock($request)); - - $this->assertEquals('/res?client_id=clientId&client_secret=clientSecret', $request->getUrl()); - } - - private function getEventMock($request = null) - { - $mock = $this->getMockBuilder('Guzzle\Common\Event')->getMock(); - - if ($request) { - $mock->expects($this->any()) - ->method('offsetGet') - ->will($this->returnValue($request)); - } - - return $mock; - } -} diff --git a/test/Github/Tests/HttpClient/Listener/ErrorListenerTest.php b/test/Github/Tests/HttpClient/Listener/ErrorListenerTest.php deleted file mode 100644 index b491d3d58c5..00000000000 --- a/test/Github/Tests/HttpClient/Listener/ErrorListenerTest.php +++ /dev/null @@ -1,239 +0,0 @@ -getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(false)); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @expectedException \Github\Exception\ApiLimitExceedException - */ - public function shouldFailWhenApiLimitWasExceed() - { - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->at(1)) - ->method('getHeader') - ->with('X-RateLimit-Remaining') - ->will($this->returnValue(0)); - $response->expects($this->at(2)) - ->method('getHeader') - ->with('X-RateLimit-Limit') - ->will($this->returnValue(5000)); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @expectedException \Github\Exception\RuntimeException - */ - public function shouldNotPassWhenContentWasNotValidJson() - { - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->at(1)) - ->method('getHeader') - ->with('X-RateLimit-Remaining') - ->will($this->returnValue(5000)); - $response->expects($this->at(2)) - ->method('getHeader') - ->with('X-RateLimit-Limit') - ->will($this->returnValue(5000)); - - $response->expects($this->once()) - ->method('getBody') - ->will($this->returnValue('fail')); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @expectedException \Github\Exception\RuntimeException - */ - public function shouldNotPassWhenContentWasValidJsonButStatusIsNotCovered() - { - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->at(1)) - ->method('getHeader') - ->with('X-RateLimit-Remaining') - ->will($this->returnValue(5000)); - $response->expects($this->at(2)) - ->method('getHeader') - ->with('X-RateLimit-Limit') - ->will($this->returnValue(5000)); - $response->expects($this->once()) - ->method('getBody') - ->will($this->returnValue(json_encode(array('message' => 'test')))); - $response->expects($this->any()) - ->method('getStatusCode') - ->will($this->returnValue(404)); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @expectedException \Github\Exception\ErrorException - */ - public function shouldNotPassWhen400IsSent() - { - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->at(1)) - ->method('getHeader') - ->with('X-RateLimit-Remaining') - ->will($this->returnValue(5000)); - $response->expects($this->at(2)) - ->method('getHeader') - ->with('X-RateLimit-Limit') - ->will($this->returnValue(5000)); - $response->expects($this->once()) - ->method('getBody') - ->will($this->returnValue(json_encode(array('message' => 'test')))); - $response->expects($this->once()) - ->method('getContentType') - ->will($this->returnValue('application/json')); - $response->expects($this->any()) - ->method('getStatusCode') - ->will($this->returnValue(400)); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @dataProvider getErrorCodesProvider - * @expectedException \Github\Exception\ValidationFailedException - */ - public function shouldNotPassWhen422IsSentWithErrorCode($errorCode) - { - $content = json_encode(array( - 'message' => 'Validation Failed', - 'errors' => array( - array( - 'code' => $errorCode, - 'field' => 'test', - 'value' => 'wrong', - 'resource' => 'fake' - ) - ) - )); - - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->at(1)) - ->method('getHeader') - ->with('X-RateLimit-Remaining') - ->will($this->returnValue(5000)); - $response->expects($this->at(2)) - ->method('getHeader') - ->with('X-RateLimit-Limit') - ->will($this->returnValue(5000)); - $response->expects($this->once()) - ->method('getContentType') - ->will($this->returnValue('application/json')); - $response->expects($this->once()) - ->method('getBody') - ->will($this->returnValue($content)); - $response->expects($this->any()) - ->method('getStatusCode') - ->will($this->returnValue(422)); - - $listener = new ErrorListener(array('api_limit' => 5000)); - $listener->onRequestError($this->getEventMock($response)); - } - - /** - * @test - * @expectedException \Github\Exception\TwoFactorAuthenticationRequiredException - */ - public function shouldThrowTwoFactorAuthenticationRequiredException() - { - $response = $this->getMockBuilder('Guzzle\Http\Message\Response')->disableOriginalConstructor()->getMock(); - $response->expects($this->once()) - ->method('isClientError') - ->will($this->returnValue(true)); - $response->expects($this->any()) - ->method('getStatusCode') - ->will($this->returnValue(401)); - $response->expects($this->any()) - ->method('getHeader') - ->will($this->returnCallback(function ($name) { - switch ($name) { - case 'X-RateLimit-Remaining': - return 5000; - case 'X-GitHub-OTP': - return 'required; sms'; - case 'X-RateLimit-Limit': - return 5000; - } - })); - $response->expects($this->any()) - ->method('hasHeader') - ->with('X-GitHub-OTP') - ->will($this->returnValue(true)); - - $listener = new ErrorListener(array()); - $listener->onRequestError($this->getEventMock($response)); - } - - public function getErrorCodesProvider() - { - return array( - array('missing'), - array('missing_field'), - array('invalid'), - array('already_exists'), - ); - } - - private function getEventMock($response) - { - $mock = $this->getMockBuilder('Guzzle\Common\Event')->getMock(); - - $request = $this->getMockBuilder('Guzzle\Http\Message\Request')->disableOriginalConstructor()->getMock(); - - $request->expects($this->any()) - ->method('getResponse') - ->will($this->returnValue($response)); - - $mock->expects($this->any()) - ->method('offsetGet') - ->will($this->returnValue($request)); - - return $mock; - } -} diff --git a/test/Github/Tests/HttpClient/Message/ResponseMediatorTest.php b/test/Github/Tests/HttpClient/Message/ResponseMediatorTest.php new file mode 100644 index 00000000000..2880f95fd28 --- /dev/null +++ b/test/Github/Tests/HttpClient/Message/ResponseMediatorTest.php @@ -0,0 +1,88 @@ + + */ +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' => 'http://github.com', + 'next' => 'http://github.com', + 'prev' => 'http://github.com', + 'last' => 'http://github.com' + ); + + // response mock + $response = new Response(200, array('link'=>$header)); + $result = ResponseMediator::getPagination($response); + + $this->assertEquals($pagination, $result); + } + + public function testGetHeader() + { + $header = 'application/json'; + $response = new Response( + 200, + array('Content-Type'=> $header) + ); + + $this->assertEquals($header, ResponseMediator::getHeader($response, 'content-type')); + } +} diff --git a/test/Github/Tests/Mock/PaginatedResponse.php b/test/Github/Tests/Mock/PaginatedResponse.php new file mode 100644 index 00000000000..13d2e815fc0 --- /dev/null +++ b/test/Github/Tests/Mock/PaginatedResponse.php @@ -0,0 +1,49 @@ + + */ +class PaginatedResponse extends Response +{ + protected $loopCount; + + protected $content; + + public function __construct($loopCount, array $content = []) + { + $this->loopCount = $loopCount; + $this->content = $content; + + parent::__construct(200, array('Content-Type'=>'application/json'), \GuzzleHttp\Psr7\stream_for(json_encode($content))); + } + + public function getHeader($header) + { + if ($header === 'Link') { + if ($this->loopCount > 1) { + $header = array(sprintf('; rel="next"', $this->loopCount)); + } else { + $header = array('; rel="prev"'); + } + + $this->loopCount--; + + return $header; + } + + return parent::getHeader($header); + } + + public function hasHeader($header) + { + if ($header === 'Link') { + return true; + } + + return parent::hasHeader($header); + } +} diff --git a/test/Github/Tests/Mock/TestHttpClient.php b/test/Github/Tests/Mock/TestHttpClient.php deleted file mode 100644 index 7b52d8807cc..00000000000 --- a/test/Github/Tests/Mock/TestHttpClient.php +++ /dev/null @@ -1,65 +0,0 @@ - array(), - 'post' => array(), - 'patch' => array(), - 'put' => array(), - 'delete' => array(), - ); - public $options = array(); - public $headers = array(); - - public function authenticate($tokenOrLogin, $password, $authMethod) - { - $this->authenticated = true; - } - - public function setOption($key, $value) - { - $this->options[$key] = $value; - } - - public function setHeaders(array $headers) - { - $this->headers = $headers; - } - - public function get($path, array $parameters = array(), array $headers = array()) - { - $this->requests['get'][] = $path; - } - - public function post($path, $body = null, array $headers = array()) - { - $this->requests['post'][] = $path; - } - - public function patch($path, $body = null, array $headers = array()) - { - $this->requests['patch'][] = $path; - } - - public function put($path, array $options = array(), array $headers = array()) - { - $this->requests['put'][] = $path; - } - - public function delete($path, $body = null, array $headers = array()) - { - $this->requests['delete'][] = $path; - } - - public function request($path, array $parameters = array(), $httpMethod = 'GET', array $headers = array()) - { - $this->requests[$httpMethod][] = $path; - } -} diff --git a/test/Github/Tests/Mock/TestResponse.php b/test/Github/Tests/Mock/TestResponse.php deleted file mode 100644 index 0ca86483439..00000000000 --- a/test/Github/Tests/Mock/TestResponse.php +++ /dev/null @@ -1,44 +0,0 @@ -loopCount = $loopCount; - $this->content = $content; - } - - /** - * {@inheritDoc} - */ - public function getBody($asString = false) - { - return json_encode($this->content); - } - - public function getHeader($header = null) - { - if ($this->loopCount) { - $header = sprintf('; rel="next"', $this->loopCount); - } else { - $header = '; rel="prev"'; - } - - $this->loopCount--; - - return $header; - } - - public function getContentType() - { - return 'application/json'; - } -} diff --git a/test/Github/Tests/ResultPagerTest.php b/test/Github/Tests/ResultPagerTest.php index 494731523db..af9e767a5de 100644 --- a/test/Github/Tests/ResultPagerTest.php +++ b/test/Github/Tests/ResultPagerTest.php @@ -2,15 +2,20 @@ namespace Github\Tests; -use Github; -use Github\HttpClient\HttpClientInterface; -use Github\Tests\Mock\TestResponse; +use Github\Api\Organization; +use Github\Api\Organization\Members; +use Github\Api\Search; +use Github\Client; +use Github\ResultPager; +use Github\Tests\Mock\PaginatedResponse; +use Http\Client\HttpClient; /** * ResultPagerTest. * * @author Ramon de la Fuente * @author Mitchel Verschoof + * @author Tobias Nyholm */ class ResultPagerTest extends \PHPUnit_Framework_TestCase { @@ -21,32 +26,28 @@ class ResultPagerTest extends \PHPUnit_Framework_TestCase */ public function shouldGetAllResults() { - $amountLoops = 3; - $content = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); - $responseMock = new TestResponse($amountLoops, $content); + $amountLoops = 3; + $content = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + $response = new PaginatedResponse($amountLoops, $content); // httpClient mock - $httpClientMock = $this->getHttpClientMock($responseMock); + $httpClientMock = $this->getMock('Http\Client\HttpClient', array('sendRequest')); $httpClientMock ->expects($this->exactly($amountLoops)) - ->method('get') - ->will($this->returnValue($responseMock)); + ->method('sendRequest') + ->will($this->returnValue($response)); - $clientMock = $this->getClientMock($httpClientMock); + $client = new Client($httpClientMock); // memberApi Mock - $memberApiMock = $this->getApiMock('Github\Api\Organization\Members'); - $memberApiMock - ->expects($this->once()) - ->method('all') - ->will($this->returnValue(array())); + $memberApi = new Members($client); - $method = 'all'; + $method = 'all'; $parameters = array('netwerven'); // Run fetchAll on result paginator - $paginator = new Github\ResultPager($clientMock); - $result = $paginator->fetchAll($memberApiMock, $method, $parameters); + $paginator = new ResultPager($client); + $result = $paginator->fetchAll($memberApi, $method, $parameters); $this->assertEquals($amountLoops * count($content), count($result)); } @@ -66,229 +67,47 @@ public function shouldGetAllResults() */ public function shouldGetAllSearchResults() { - $amountLoops = 3; + $amountLoops = 3; - $content = array( + $content = array( 'total_count' => 12, 'items' => array(1, 2, 3, 4) ); - $responseMock = new TestResponse($amountLoops, $content); + $response = new PaginatedResponse($amountLoops, $content); - $httpClientMock = $this->getHttpClientMock($responseMock); + // httpClient mock + $httpClientMock = $this->getMock('Http\Client\HttpClient', array('sendRequest')); $httpClientMock ->expects($this->exactly($amountLoops)) - ->method('get') - ->will($this->returnValue($responseMock)); - - $clientMock = $this->getClientMock($httpClientMock); + ->method('sendRequest') + ->will($this->returnValue($response)); - $searchApiMock = $this->getApiMock('Github\Api\Search'); - $searchApiMock - ->expects($this->once()) - ->method('users') - ->will($this->returnValue(array())); + $client = new Client($httpClientMock); - $method = 'users'; - $paginator = new Github\ResultPager($clientMock); - $result = $paginator->fetchAll($searchApiMock, $method, array('knplabs')); + $searchApi = new Search($client); + $method = 'users'; + $paginator = new ResultPager($client); + $result = $paginator->fetchAll($searchApi, $method, array('knplabs')); $this->assertEquals($amountLoops * count($content['items']), count($result)); } - /** - * @test - * - * description fetch - */ - public function shouldGetSomeResults() - { - $pagination = array('next' => 'http://github.com/next'); - $resultContent = 'organization test'; - - $responseMock = $this->getResponseMock('; rel="next"'); - $httpClient = $this->getHttpClientMock($responseMock); - $client = $this->getClientMock($httpClient); - - $organizationApiMock = $this->getApiMock('Github\Api\Organization'); - - $organizationApiMock - ->expects($this->once()) - ->method('show') - ->with('github') - ->will($this->returnValue($resultContent)); - - $paginator = new Github\ResultPager($client); - $result = $paginator->fetch($organizationApiMock, 'show', array('github')); - - $this->assertEquals($resultContent, $result); - $this->assertEquals($pagination, $paginator->getPagination()); - } - - /** - * @test - * - * description postFetch - */ - public function postFetch() - { - $header = <<; rel="first", -; rel="next", -; rel="prev", -; rel="last", -TEXT; - - $pagination = array( - 'first' => 'http://github.com', - 'next' => 'http://github.com', - 'prev' => 'http://github.com', - 'last' => 'http://github.com' - ); - - // response mock - $responseMock = $this->getMock('Guzzle\Http\Message\Response', array(), array(200)); - $responseMock - ->expects($this->any()) - ->method('getHeader') - ->with('Link') - ->will($this->returnValue($header)); - - $httpClient = $this->getHttpClientMock($responseMock); - $client = $this->getClientMock($httpClient); - - $paginator = new Github\ResultPager($client); - $paginator->postFetch(); - - $this->assertEquals($paginator->getPagination(), $pagination); - } - - /** - * @test - * - * description fetchNext - */ - public function fetchNext() + public function testFetch() { - $header = '; rel="next"'; - $pagination = array('next' => 'http://github.com/next'); - $resultContent = 'fetch test'; - - $responseMock = $this->getResponseMock($header); - $responseMock - ->expects($this->once()) - ->method('getBody') - ->will($this->returnValue($resultContent)); - // Expected 2 times, 1 for setup and 1 for the actual test - $responseMock - ->expects($this->exactly(2)) - ->method('getHeader') - ->with('Link'); + $result = 'foo'; + $method = 'bar'; + $parameters = array('baz'); + $api = $this->getMock('Github\Api\ApiInterface'); - $httpClient = $this->getHttpClientMock($responseMock); + $paginator = $this->getMock('Github\ResultPager', array('callApi', 'postFetch'), array(), '', false); + $paginator->expects($this->once()) + ->method('callApi') + ->with($api, $method, $parameters) + ->willReturn($result); - $httpClient - ->expects($this->once()) - ->method('get') - ->with($pagination['next']) - ->will($this->returnValue($responseMock)); - - $client = $this->getClientMock($httpClient); - - $paginator = new Github\ResultPager($client); - $paginator->postFetch(); - - $this->assertEquals($paginator->fetchNext(), $resultContent); - } - - /** - * @test - * - * description hasNext - */ - public function shouldHaveNext() - { - $responseMock = $this->getResponseMock('; rel="next"'); - $httpClient = $this->getHttpClientMock($responseMock); - $client = $this->getClientMock($httpClient); - - $paginator = new Github\ResultPager($client); - $paginator->postFetch(); - - $this->assertEquals($paginator->hasNext(), true); - $this->assertEquals($paginator->hasPrevious(), false); - } - - /** - * @test - * - * description hasPrevious - */ - public function shouldHavePrevious() - { - $responseMock = $this->getResponseMock('; rel="prev"'); - $httpClient = $this->getHttpClientMock($responseMock); - $client = $this->getClientMock($httpClient); - - $paginator = new Github\ResultPager($client); - $paginator->postFetch(); - - $this->assertEquals($paginator->hasPrevious(), true); - $this->assertEquals($paginator->hasNext(), false); - } - - protected function getResponseMock($header) - { - // response mock - $responseMock = $this->getMock('Guzzle\Http\Message\Response', array(), array(200)); - $responseMock - ->expects($this->any()) - ->method('getHeader') - ->with('Link') - ->will($this->returnValue($header)); - - return $responseMock; - } - - protected function getClientMock(HttpClientInterface $httpClient = null) - { - // if no httpClient isset use the default HttpClient mock - if (!$httpClient) { - $httpClient = $this->getHttpClientMock(); - } - - $client = new \Github\Client($httpClient); - $client->setHttpClient($httpClient); - - return $client; - } - - protected function getHttpClientMock($responseMock = null) - { - // mock the client interface - $clientInterfaceMock = $this->getMock('Guzzle\Http\Client', array('send')); - $clientInterfaceMock - ->expects($this->any()) - ->method('send'); - - // create the httpClient mock - $httpClientMock = $this->getMock('Github\HttpClient\HttpClient', array(), array(array(), $clientInterfaceMock)); - - if ($responseMock) { - $httpClientMock - ->expects($this->any()) - ->method('getLastResponse') - ->will($this->returnValue($responseMock)); - } - - return $httpClientMock; - } - - protected function getApiMock($apiClass) - { - $client = $this->getClientMock(); + $paginator->expects($this->once()) + ->method('postFetch'); - return $this->getMockBuilder($apiClass) - ->setConstructorArgs(array($client)) - ->getMock(); + $this->assertEquals($result, $paginator->fetch($api, $method, $parameters)); } }