diff --git a/build/phpunit.xml b/build/phpunit.xml index 5de965e..372bff1 100644 --- a/build/phpunit.xml +++ b/build/phpunit.xml @@ -32,7 +32,7 @@ - src/ + ../src/ diff --git a/src/Api.php b/src/Api.php index 2919ddf..6924720 100644 --- a/src/Api.php +++ b/src/Api.php @@ -2,9 +2,9 @@ namespace Potherca\Flysystem\Github; +use Github\Api\ApiInterface; use Github\Api\GitData; use Github\Api\Repo; -use Github\Api\Repository\Contents; use Github\Client; use Github\Exception\RuntimeException; use League\Flysystem\AdapterInterface; @@ -13,14 +13,16 @@ /** * Facade class for the Github Api Library */ -class Api implements ApiInterface +class Api implements \Potherca\Flysystem\Github\ApiInterface { ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\ const ERROR_NO_NAME = 'Could not set name for entry'; const ERROR_NOT_FOUND = 'Not Found'; - const API_GIT_DATA = 'git'; - const API_REPO = 'repo'; + const API_GIT_DATA = 'gitData'; + const API_REPOSITORY = 'repo'; + const API_REPOSITORY_COMMITS = 'commits'; + const API_REPOSITORY_CONTENTS = 'contents'; const KEY_BLOB = 'blob'; const KEY_DIRECTORY = 'dir'; @@ -36,20 +38,27 @@ class Api implements ApiInterface const KEY_TREE = 'tree'; const KEY_TYPE = 'type'; const KEY_VISIBILITY = 'visibility'; - + const GITHUB_API_URL = 'https://api.github.com'; const GITHUB_URL = 'https://github.com'; - const MIME_TYPE_DIRECTORY = 'directory'; + const MIME_TYPE_DIRECTORY = 'directory'; // or application/x-directory + + const NOT_RECURSIVE = false; + const RECURSIVE = true; + /** @var ApiInterface[] */ + private $apiCollection = []; /** @var Client */ private $client; - /** @var Contents */ - private $contents; - /** @var SettingsInterface */ - private $settings; + /** @var array */ + private $commits = []; /** @var bool */ private $isAuthenticationAttempted = false; + /** @var array */ + private $metadata = []; + /** @var SettingsInterface */ + private $settings; //////////////////////////// SETTERS AND GETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\ /** @@ -61,41 +70,66 @@ class Api implements ApiInterface */ private function getApi($name) { - $this->authenticate(); - return $this->client->api($name); + $this->assureAuthenticated(); + + if ($this->hasKey($this->apiCollection, $name) === false) { + $this->apiCollection[$name] = $this->client->api($name); + } + + return $this->apiCollection[$name]; } /** - * @return GitData + * @param $name + * @param $api + * @return ApiInterface + */ + private function getApiFrom($name, $api) + { + if ($this->hasKey($this->apiCollection, $name) === false) { + $this->apiCollection[$name] = $api->{$name}(); + } + return $this->apiCollection[$name]; + } + + /** + * @return \Github\Api\Repository\Commits * * @throws \Github\Exception\InvalidArgumentException */ - private function getGitDataApi() + private function getCommitsApi() { - return $this->getApi(self::API_GIT_DATA); + return $this->getApiFrom(self::API_REPOSITORY_COMMITS, $this->getRepositoryApi()); } /** - * @return Repo + * @return \Github\Api\Repository\Contents * * @throws \Github\Exception\InvalidArgumentException */ - private function getRepositoryApi() + private function getContentApi() { - return $this->getApi(self::API_REPO); + return $this->getApiFrom(self::API_REPOSITORY_CONTENTS, $this->getRepositoryApi()); } /** - * @return \Github\Api\Repository\Contents + * @return GitData * * @throws \Github\Exception\InvalidArgumentException */ - private function getRepositoryContent() + private function getGitDataApi() { - if ($this->contents === null) { - $this->contents = $this->getRepositoryApi()->contents(); - } - return $this->contents; + return $this->getApi(self::API_GIT_DATA); + } + + /** + * @return Repo + * + * @throws \Github\Exception\InvalidArgumentException + */ + private function getRepositoryApi() + { + return $this->getApi(self::API_REPOSITORY); } //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ @@ -118,7 +152,9 @@ final public function __construct(Client $client, SettingsInterface $settings) */ final public function exists($path) { - return $this->getRepositoryContent()->exists( + $path = $this->normalizePathName($path); + + return $this->getContentApi()->exists( $this->settings->getVendor(), $this->settings->getPackage(), $path, @@ -136,7 +172,9 @@ final public function exists($path) */ final public function getFileContents($path) { - return $this->getRepositoryContent()->download( + $path = $this->normalizePathName($path); + + return $this->getContentApi()->download( $this->settings->getVendor(), $this->settings->getPackage(), $path, @@ -153,13 +191,7 @@ final public function getFileContents($path) */ final public function getLastUpdatedTimestamp($path) { - $commits = $this->commitsForFile($path); - - $updated = array_shift($commits); - - $time = new \DateTime($updated['commit']['committer']['date']); - - return ['timestamp' => $time->getTimestamp()]; + return $this->filterCommits($path, 'reset'); } /** @@ -171,13 +203,7 @@ final public function getLastUpdatedTimestamp($path) */ final public function getCreatedTimestamp($path) { - $commits = $this->commitsForFile($path); - - $created = array_pop($commits); - - $time = new \DateTime($created['commit']['committer']['date']); - - return ['timestamp' => $time->getTimestamp()]; + return $this->filterCommits($path, 'end'); } /** @@ -187,56 +213,36 @@ final public function getCreatedTimestamp($path) * * @throws \Github\Exception\InvalidArgumentException * @throws \Github\Exception\RuntimeException + * @throws \League\Flysystem\NotSupportedException */ final public function getMetaData($path) { - try { - $metadata = $this->getRepositoryContent()->show( - $this->settings->getVendor(), - $this->settings->getPackage(), - $path, - $this->settings->getReference() - ); - } catch (RuntimeException $exception) { - if ($exception->getMessage() === self::ERROR_NOT_FOUND) { - $metadata = false; - } else { - throw $exception; + $path = $this->normalizePathName($path); + + if ($this->hasKey($this->metadata, $path) === false) { + try { + $metadata = $this->getContentApi()->show( + $this->settings->getVendor(), + $this->settings->getPackage(), + $path, + $this->settings->getReference() + ); + } catch (RuntimeException $exception) { + if ($exception->getMessage() === self::ERROR_NOT_FOUND) { + $metadata = false; + } else { + throw $exception; + } + } + + if ($this->isMetadataForDirectory($metadata) === true) { + $metadata = $this->metadataForDirectory($path); } - } - - if (is_array($metadata) === true && $this->isMetadataForDirectory($metadata) === true) { - /** @var $metadata array */ - $project = sprintf('%s/%s', $this->settings->getVendor(), $this->settings->getPackage()); - $reference = $this->settings->getReference(); - - $url = sprintf( - '%s/repos/%s/contents/%s?ref=%s', - self::GITHUB_API_URL, - $project, - trim($path, '/'), - $reference - ); - $htmlUrl = sprintf( - '%s/%s/blob/%s/%s', - self::GITHUB_URL, - $project, - $reference, - trim($path, '/') - ); - $metadata = [ - self::KEY_TYPE => self::KEY_DIRECTORY, - 'url' => $url, - 'html_url' => $htmlUrl, - '_links' => [ - 'self' => $url, - 'html' => $htmlUrl - ] - ]; + $this->metadata[$path] = $metadata; } - return $metadata; + return $this->metadata[$path]; } /** @@ -244,10 +250,13 @@ final public function getMetaData($path) * @param bool $recursive * * @return array + * * @throws \Github\Exception\InvalidArgumentException */ - final public function getTreeMetadata($path, $recursive) + final public function getDirectoryContents($path, $recursive) { + $path = $this->normalizePathName($path); + // If $info['truncated'] is `true`, the number of items in the tree array // exceeded the github maximum limit. If we need to fetch more items, // multiple calls will be needed @@ -256,36 +265,14 @@ final public function getTreeMetadata($path, $recursive) $this->settings->getVendor(), $this->settings->getPackage(), $this->settings->getReference(), - true //@NOTE: To retrieve all needed date the 'recursive' flag should always be 'true' + self::RECURSIVE //@NOTE: To retrieve all needed date the 'recursive' flag should always be 'true' ); - $path = rtrim($path, '/') . '/'; + $treeData = $this->addTimestamps($info[self::KEY_TREE]); - $treeMetadata = $this->extractMetaDataFromTreeInfo($info[self::KEY_TREE], $path, $recursive); - - $normalizeTreeMetadata = $this->normalizeTreeMetadata($treeMetadata); - - $directoryTimestamp = 0000000000; + $filteredTreeData = $this->filterTreeData($treeData, $path, $recursive); - array_walk($normalizeTreeMetadata, function (&$entry) use (&$directoryTimestamp) { - if ($this->hasKey($entry, self::KEY_TIMESTAMP) === false - || $entry[self::KEY_TIMESTAMP] === false - ) { - $timestamp = $this->getCreatedTimestamp($entry[self::KEY_PATH])['timestamp']; - - $entry[self::KEY_TIMESTAMP] = $timestamp; - - if ($timestamp > $directoryTimestamp) { - $directoryTimestamp = $timestamp; - } - } - }); - - /* @FIXME: It might be wise to use a filter to find the right entry instead of always using the first entry in the array. */ - - $normalizeTreeMetadata[0]['timestamp'] = $directoryTimestamp; - - return $normalizeTreeMetadata; + return $this->normalizeTreeData($filteredTreeData); } /** @@ -299,11 +286,14 @@ final public function getTreeMetadata($path, $recursive) */ final public function guessMimeType($path) { + $path = $this->normalizePathName($path); + //@NOTE: The github API does not return a MIME type, so we have to guess :-( $meta = $this->getMetaData($path); + /** @noinspection OffsetOperationsInspection *//* @NOTE: The existence of $meta[self::KEY_TYPE] has been validated by `hasKey`. */ if ($this->hasKey($meta, self::KEY_TYPE) && $meta[self::KEY_TYPE] === self::KEY_DIRECTORY) { - $mimeType = self::MIME_TYPE_DIRECTORY; // or application/x-directory + $mimeType = self::MIME_TYPE_DIRECTORY; } else { $content = $this->getFileContents($path); $mimeType = MimeType::detectByContent($content); @@ -317,12 +307,12 @@ final public function guessMimeType($path) * * @throws \Github\Exception\InvalidArgumentException If no authentication method was given */ - private function authenticate() + private function assureAuthenticated() { if ($this->isAuthenticationAttempted === false) { $credentials = $this->settings->getCredentials(); - if (empty($credentials) === false) { + if (count($credentials) !== 0) { $credentials = array_replace( [null, null, null], $credentials @@ -345,21 +335,18 @@ private function authenticate() * * @return array */ - private function extractMetaDataFromTreeInfo(array $tree, $path, $recursive) + private function filterTreeData(array $tree, $path, $recursive) { - $matchPath = substr($path, 0, -1); - $length = abs(strlen($matchPath) - 1); + $length = strlen($path); - $metadata = array_filter($tree, function ($entry) use ($matchPath, $recursive, $length) { + $metadata = array_filter($tree, function ($entry) use ($path, $recursive, $length) { $match = false; - $entryPath = $entry[self::KEY_PATH]; - - if ($matchPath === '' || strpos($entryPath, $matchPath) === 0) { - if ($recursive === true) { + if ($path === '' || strpos($entry[self::KEY_PATH], $path) === 0) { + if ($recursive === self::RECURSIVE) { $match = true; } else { - $match = ($matchPath !== '' || strpos($entryPath, '/', $length) === false); + $match = ($path !== '' || strpos($entry[self::KEY_PATH], '/', $length) === false); } } @@ -385,32 +372,30 @@ private function guessVisibility($permissions) } /** - * @param array $metadata + * @param array $treeData * * @return array + * + * @throws \Github\Exception\InvalidArgumentException */ - private function normalizeTreeMetadata($metadata) + private function normalizeTreeData($treeData) { - $result = []; - - if (is_array(current($metadata)) === false) { - $metadata = [$metadata]; + if (is_array(current($treeData)) === false) { + $treeData = [$treeData]; } - foreach ($metadata as $entry) { + $normalizedTreeData = array_map(function ($entry) { $this->setEntryName($entry); $this->setEntryType($entry); $this->setEntryVisibility($entry); $this->setDefaultValue($entry, self::KEY_CONTENTS); $this->setDefaultValue($entry, self::KEY_STREAM); - $this->setDefaultValue($entry, self::KEY_TIMESTAMP); - - $result[] = $entry; - } + return $entry; + }, $treeData); - return $result; + return $normalizedTreeData; } /** @@ -422,14 +407,18 @@ private function normalizeTreeMetadata($metadata) */ private function commitsForFile($path) { - return $this->getRepositoryApi()->commits()->all( - $this->settings->getVendor(), - $this->settings->getPackage(), - array( - 'sha' => $this->settings->getBranch(), - 'path' => $path - ) - ); + if ($this->hasKey($this->commits, $path) === false) { + $this->commits[$path] = $this->getCommitsApi()->all( + $this->settings->getVendor(), + $this->settings->getPackage(), + array( + 'sha' => $this->settings->getBranch(), + 'path' => $path + ) + ); + } + + return $this->commits[$path]; } /** @@ -460,7 +449,10 @@ private function setEntryType(&$entry) case self::KEY_TREE: $entry[self::KEY_TYPE] = self::KEY_DIRECTORY; break; + //@CHECKME: what should the 'default' be? Throw exception for unknown? } + } else { + $entry[self::KEY_TYPE] = false; } } @@ -501,10 +493,12 @@ private function isMetadataForDirectory($metadata) { $isDirectory = false; - $keys = array_keys($metadata); + if (is_array($metadata) === true) { + $keys = array_keys($metadata); - if ($keys[0] === 0) { - $isDirectory = true; + if ($keys[0] === 0) { + $isDirectory = true; + } } return $isDirectory; @@ -526,4 +520,129 @@ private function hasKey(&$subject, $key) return $keyExists; } + + /** + * @param array $treeMetadata + * @param $path + * + * @return int + * + * @throws \Github\Exception\InvalidArgumentException + */ + private function getDirectoryTimestamp(array $treeMetadata, $path) + { + $directoryTimestamp = 0000000000; + + $filteredTreeData = $this->filterTreeData($treeMetadata, $path, self::RECURSIVE); + + array_walk($filteredTreeData, function ($entry) use (&$directoryTimestamp, $path) { + if ($entry[self::KEY_TYPE] === self::KEY_FILE + && strpos($entry[self::KEY_PATH], $path) === 0 + ) { + // @CHECKME: Should the directory Timestamp reflect the `getCreatedTimestamp` or `getLastUpdatedTimestamp`? + $timestamp = $this->getCreatedTimestamp($entry[self::KEY_PATH])[self::KEY_TIMESTAMP]; + + if ($timestamp > $directoryTimestamp) { + $directoryTimestamp = $timestamp; + } + } + }); + + return $directoryTimestamp; + } + + private function normalizePathName($path) + { + return trim($path, '/'); + } + + /** + * @param $path + * @return array + * @throws \League\Flysystem\NotSupportedException + * @throws \Github\Exception\RuntimeException + * + * @throws \Github\Exception\InvalidArgumentException + */ + private function metadataForDirectory($path) + { + $reference = $this->settings->getReference(); + $project = sprintf('%s/%s', $this->settings->getVendor(), $this->settings->getPackage()); + + $url = sprintf( + '%s/repos/%s/contents/%s?ref=%s', + self::GITHUB_API_URL, + $project, + $path, + $reference + ); + $htmlUrl = sprintf( + '%s/%s/blob/%s/%s', + self::GITHUB_URL, + $project, + $reference, + $path + ); + + $directoryContents = $this->getDirectoryContents($path, self::RECURSIVE); + + $directoryMetadata = array_filter($directoryContents, function ($entry) use ($path) { + return $entry[self::KEY_PATH] === $path; + }); + + $metadata = array_merge( + $directoryMetadata[0], + [ + self::KEY_TYPE => self::KEY_DIRECTORY, + 'url' => $url, + 'html_url' => $htmlUrl, + '_links' => [ + 'self' => $url, + 'html' => $htmlUrl + ] + ] + ); + + return $metadata; + } + + /** + * @param array $treeData + * + * @return array + * + * @throws \Github\Exception\InvalidArgumentException + */ + private function addTimestamps(array $treeData) + { + return array_map(function ($entry) use ($treeData) { + if ($entry[self::KEY_TYPE] === self::KEY_DIRECTORY) { + $timestamp = $this->getDirectoryTimestamp($treeData, $entry[self::KEY_PATH]); + } else { + // @CHECKME: Should the Timestamp reflect the `getCreatedTimestamp` or `getLastUpdatedTimestamp`? + $timestamp = $this->getCreatedTimestamp($entry[self::KEY_PATH])[self::KEY_TIMESTAMP]; + } + $entry[self::KEY_TIMESTAMP] = $timestamp; + + return $entry; + }, $treeData); + } + + /** + * @param $path + * @param $function + * @return array + */ + private function filterCommits($path, callable $function) + { + $path = $this->normalizePathName($path); + + $commits = $this->commitsForFile($path); + + $subject = $function($commits); + + $time = new \DateTime($subject['commit']['committer']['date']); + + return [self::KEY_TIMESTAMP => $time->getTimestamp()]; + } } diff --git a/src/ApiInterface.php b/src/ApiInterface.php index d5168de..19ade72 100644 --- a/src/ApiInterface.php +++ b/src/ApiInterface.php @@ -42,7 +42,7 @@ public function getMetaData($path); * * @return array */ - public function getTreeMetadata($path, $recursive); + public function getDirectoryContents($path, $recursive); /** * @param string $path diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php index 1379ecc..d63cc48 100644 --- a/src/GithubAdapter.php +++ b/src/GithubAdapter.php @@ -187,7 +187,7 @@ public function read($path) */ public function listContents($path = '/', $recursive = false) { - $contents = $this->getApi()->getTreeMetadata($path, $recursive); + $contents = $this->getApi()->getDirectoryContents($path, $recursive); if ($this->isDirectoryContents($contents) === false) { $contents = []; @@ -254,7 +254,7 @@ public function getTimestamp($path) public function getVisibility($path) { $recursive = false; - $metadata = $this->getApi()->getTreeMetadata($path, $recursive); + $metadata = $this->getApi()->getDirectoryContents($path, $recursive); return $metadata[0]; } diff --git a/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcommits.json b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcommits.json new file mode 100644 index 0000000..36e888c --- /dev/null +++ b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcommits.json @@ -0,0 +1,278 @@ +[ + { + "sha": "676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "commit": { + "author": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:54:55Z" + }, + "committer": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:55:17Z" + }, + "message": "Adds a directory.", + "tree": { + "sha": "f6deabf5e5e195c06d4e7820f4e24be11cb9ae40", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/f6deabf5e5e195c06d4e7820f4e24be11cb9ae40" + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/commits/676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "comment_count": 0 + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "html_url": "https://github.com/potherca-bot/test-repository/commit/676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "comments_url": "https://api.github.com/repos/potherca-bot/test-repository/commits/676a09fd1c7df28938e8c12dc5d9f3c3271f0249/comments", + "author": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "19278cb740438ddeb2013af182e410270d98bb1a", + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/19278cb740438ddeb2013af182e410270d98bb1a", + "html_url": "https://github.com/potherca-bot/test-repository/commit/19278cb740438ddeb2013af182e410270d98bb1a" + } + ] + }, + { + "sha": "19278cb740438ddeb2013af182e410270d98bb1a", + "commit": { + "author": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:54:39Z" + }, + "committer": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:55:12Z" + }, + "message": "Adds a file.", + "tree": { + "sha": "54625561b164dd772eb072422904f2c56476dd50", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/54625561b164dd772eb072422904f2c56476dd50" + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/commits/19278cb740438ddeb2013af182e410270d98bb1a", + "comment_count": 0 + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/19278cb740438ddeb2013af182e410270d98bb1a", + "html_url": "https://github.com/potherca-bot/test-repository/commit/19278cb740438ddeb2013af182e410270d98bb1a", + "comments_url": "https://api.github.com/repos/potherca-bot/test-repository/commits/19278cb740438ddeb2013af182e410270d98bb1a/comments", + "author": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "html_url": "https://github.com/potherca-bot/test-repository/commit/2937e62cd3aa5d5ddce78fe47dffd819366807b5" + } + ] + }, + { + "sha": "2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "commit": { + "author": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:51:19Z" + }, + "committer": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2016-04-03T15:51:19Z" + }, + "message": "Adds README file.", + "tree": { + "sha": "84051791094ac74fed2dd68d4f7ab7754bcc7744", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/84051791094ac74fed2dd68d4f7ab7754bcc7744" + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/commits/2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "comment_count": 0 + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "html_url": "https://github.com/potherca-bot/test-repository/commit/2937e62cd3aa5d5ddce78fe47dffd819366807b5", + "comments_url": "https://api.github.com/repos/potherca-bot/test-repository/commits/2937e62cd3aa5d5ddce78fe47dffd819366807b5/comments", + "author": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "f0520a2efe8cb04b343f564f957f393c6bda70fc", + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/f0520a2efe8cb04b343f564f957f393c6bda70fc", + "html_url": "https://github.com/potherca-bot/test-repository/commit/f0520a2efe8cb04b343f564f957f393c6bda70fc" + } + ] + }, + { + "sha": "f0520a2efe8cb04b343f564f957f393c6bda70fc", + "commit": { + "author": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2015-12-16T07:59:30Z" + }, + "committer": { + "name": "Ben Peachey", + "email": "potherca@gmail.com", + "date": "2015-12-16T07:59:30Z" + }, + "message": "Initial Commit.", + "tree": { + "sha": "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/4b825dc642cb6eb9a060e54bf8d69288fbee4904" + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/commits/f0520a2efe8cb04b343f564f957f393c6bda70fc", + "comment_count": 0 + }, + "url": "https://api.github.com/repos/potherca-bot/test-repository/commits/f0520a2efe8cb04b343f564f957f393c6bda70fc", + "html_url": "https://github.com/potherca-bot/test-repository/commit/f0520a2efe8cb04b343f564f957f393c6bda70fc", + "comments_url": "https://api.github.com/repos/potherca-bot/test-repository/commits/f0520a2efe8cb04b343f564f957f393c6bda70fc/comments", + "author": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "Potherca", + "id": 195757, + "avatar_url": "https://avatars.githubusercontent.com/u/195757?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/Potherca", + "html_url": "https://github.com/Potherca", + "followers_url": "https://api.github.com/users/Potherca/followers", + "following_url": "https://api.github.com/users/Potherca/following{/other_user}", + "gists_url": "https://api.github.com/users/Potherca/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Potherca/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Potherca/subscriptions", + "organizations_url": "https://api.github.com/users/Potherca/orgs", + "repos_url": "https://api.github.com/users/Potherca/repos", + "events_url": "https://api.github.com/users/Potherca/events{/privacy}", + "received_events_url": "https://api.github.com/users/Potherca/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + + ] + } +] \ No newline at end of file diff --git a/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcontents%2Fa-directory.json b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcontents%2Fa-directory.json new file mode 100644 index 0000000..d7fda39 --- /dev/null +++ b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fcontents%2Fa-directory.json @@ -0,0 +1,34 @@ +[ + { + "name": "another-file.js", + "path": "a-directory/another-file.js", + "sha": "f542363e1b45aa7a33e5e731678dee18f7a1e729", + "size": 52, + "url": "https://api.github.com/repos/potherca-bot/test-repository/contents/a-directory/another-file.js?ref=master", + "html_url": "https://github.com/potherca-bot/test-repository/blob/master/a-directory/another-file.js", + "git_url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/f542363e1b45aa7a33e5e731678dee18f7a1e729", + "download_url": "https://raw.githubusercontent.com/potherca-bot/test-repository/master/a-directory/another-file.js", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/potherca-bot/test-repository/contents/a-directory/another-file.js?ref=master", + "git": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/f542363e1b45aa7a33e5e731678dee18f7a1e729", + "html": "https://github.com/potherca-bot/test-repository/blob/master/a-directory/another-file.js" + } + }, + { + "name": "readme.txt", + "path": "a-directory/readme.txt", + "sha": "27f8ec8435cb07992ecf18f9d5494ffc14948368", + "size": 31, + "url": "https://api.github.com/repos/potherca-bot/test-repository/contents/a-directory/readme.txt?ref=master", + "html_url": "https://github.com/potherca-bot/test-repository/blob/master/a-directory/readme.txt", + "git_url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/27f8ec8435cb07992ecf18f9d5494ffc14948368", + "download_url": "https://raw.githubusercontent.com/potherca-bot/test-repository/master/a-directory/readme.txt", + "type": "file", + "_links": { + "self": "https://api.github.com/repos/potherca-bot/test-repository/contents/a-directory/readme.txt?ref=master", + "git": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/27f8ec8435cb07992ecf18f9d5494ffc14948368", + "html": "https://github.com/potherca-bot/test-repository/blob/master/a-directory/readme.txt" + } + } +] \ No newline at end of file diff --git a/tests/fixtures/listContents-folder-recursive.json b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD%3Frecursive%3D1.json similarity index 94% rename from tests/fixtures/listContents-folder-recursive.json rename to tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD%3Frecursive%3D1.json index 785a69b..f350ce4 100644 --- a/tests/fixtures/listContents-folder-recursive.json +++ b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD%3Frecursive%3D1.json @@ -1,4 +1,5 @@ { + "original-source" : "https://api.github.com/repos/potherca-bot/test-repository/git/trees/HEAD?recursive=1", "sha": "676a09fd1c7df28938e8c12dc5d9f3c3271f0249", "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/676a09fd1c7df28938e8c12dc5d9f3c3271f0249", "tree": [ @@ -17,6 +18,14 @@ "sha": "30b7e362894eecb159ce0ba2921a8363cd297213", "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/30b7e362894eecb159ce0ba2921a8363cd297213" }, + { + "path": "a-file.php", + "mode": "100755", + "type": "blob", + "sha": "c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1", + "size": 117, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1" + }, { "path": "a-directory/another-file.js", "mode": "100755", @@ -32,15 +41,7 @@ "sha": "27f8ec8435cb07992ecf18f9d5494ffc14948368", "size": 31, "url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/27f8ec8435cb07992ecf18f9d5494ffc14948368" - }, - { - "path": "a-file.php", - "mode": "100755", - "type": "blob", - "sha": "c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1", - "size": 117, - "url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1" } ], "truncated": false -} \ No newline at end of file +} diff --git a/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD.json b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD.json new file mode 100644 index 0000000..04252cf --- /dev/null +++ b/tests/fixtures/repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD.json @@ -0,0 +1,30 @@ +{ + "sha": "676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/676a09fd1c7df28938e8c12dc5d9f3c3271f0249", + "tree": [ + { + "path": "README", + "mode": "100755", + "type": "blob", + "sha": "1ff3a296caf2d27828dd8c40673c88dbf99d4b3a", + "size": 58, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/1ff3a296caf2d27828dd8c40673c88dbf99d4b3a" + }, + { + "path": "a-directory", + "mode": "040000", + "type": "tree", + "sha": "30b7e362894eecb159ce0ba2921a8363cd297213", + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/trees/30b7e362894eecb159ce0ba2921a8363cd297213" + }, + { + "path": "a-file.php", + "mode": "100755", + "type": "blob", + "sha": "c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1", + "size": 117, + "url": "https://api.github.com/repos/potherca-bot/test-repository/git/blobs/c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1" + } + ], + "truncated": false +} \ No newline at end of file diff --git a/tests/integration-tests/compareToLocalCalls.php b/tests/integration-tests/compareToLocalCalls.php index 720825f..98a5641 100644 --- a/tests/integration-tests/compareToLocalCalls.php +++ b/tests/integration-tests/compareToLocalCalls.php @@ -75,7 +75,7 @@ final public function provideFiles() $localContents = $localResult->getContents(); /** @var array $contents */ $contents = $result->getContents(); - $this->compare($localContents, $contents); + $this->validateLocalAdapterValuesArePresentInGithubAdapterValues($localContents, $contents); } elseif ($localResult instanceof \League\Flysystem\File) { /** @var $localResult \League\Flysystem\File */ /** @var $result \League\Flysystem\File */ @@ -84,13 +84,14 @@ final public function provideFiles() self::assertEquals($localResult, $result); } }], + 'getMetadata' => ['callback' => [$this, 'validateLocalAdapterValuesArePresentInGithubAdapterValues']], 'getMimetype' => [], 'getSize' => [], //@FIXME: Synchronize local timestamp with remote git repo timestamp so "getTimestamp" can be tested // 'getTimestamp' => [], 'getVisibility' => [], 'has' => [], - 'listContents' => ['type' => 'dir', 'callback' => [$this, 'compare']], + 'listContents' => ['type' => 'dir', 'callback' => [$this, 'validateLocalAdapterValuesArePresentInGithubAdapterValues']], 'read' => ['type' => 'file'], 'readStream' => ['type' => 'file', 'callback' => function ($localStream, $githubStream){ self::assertEquals(stream_get_contents($localStream), stream_get_contents($githubStream)); @@ -112,6 +113,7 @@ final public function provideFiles() } } } + ksort($files); return $files; @@ -147,21 +149,31 @@ private function createFixture() * @param array $localContents * @param array $contents */ - private function compare(array $localContents, array $contents) + private function validateLocalAdapterValuesArePresentInGithubAdapterValues(array $localContents, array $contents) { - array_walk($contents, 'ksort'); - array_walk($localContents, 'ksort'); - - $localContents = array_map(function ($value) { - unset($value['timestamp']); - return $value; - }, $localContents); + if (is_array(reset($localContents))) { + array_walk($contents, 'ksort'); + array_walk($localContents, 'ksort'); + + $localContents = array_map(function ($value) { + unset($value['timestamp']); + return $value; + }, $localContents); + foreach ($localContents as $index => $localContent) { + foreach ($localContent as $key => $value) { + self::assertEquals([$key => $value], [$key => $contents[$index][$key]]); + } + } + } else { + ksort($contents); + ksort($localContents); - foreach ($localContents as $index => $localContent) { - foreach ($localContent as $key => $value) { - self::assertEquals($value, $contents[$index][$key]); + unset($localContents['timestamp']); + foreach ($localContents as $index => $localContent) { + self::assertEquals([$index => $localContent], [$index => $contents[$index]]); } } + } private function getLocalFileSystem() diff --git a/tests/unit-tests/ApiTest.php b/tests/unit-tests/ApiTest.php index c1c3a19..9e0635f 100644 --- a/tests/unit-tests/ApiTest.php +++ b/tests/unit-tests/ApiTest.php @@ -3,7 +3,9 @@ namespace Potherca\Flysystem\Github; use Github\Api\ApiInterface; +use Github\Api\GitData; use Github\Api\GitData\Trees; +use Github\Api\Repo; use Github\Api\Repository\Commits; use Github\Api\Repository\Contents; use Github\Client; @@ -14,15 +16,22 @@ * Tests for the Api class * * @coversDefaultClass \Potherca\Flysystem\Github\Api + * * @covers :: * @covers ::__construct + * + * @uses \Potherca\Flysystem\Github\Api:: */ class ApiTest extends \PHPUnit_Framework_TestCase { ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ - const MOCK_FILE_PATH = '/path/to/mock/file'; const MOCK_FILE_CONTENTS = 'Mock file contents'; - const MOCK_FOLDER_PATH = 'a-directory'; + const MOCK_FILE_PATH = '/a-directory/another-file.js'; + const MOCK_FOLDER_PATH = 'a-directory/'; + const MOCK_PACKAGE = 'mockPackage'; + const MOCK_REFERENCE = 'mockReference'; + const MOCK_VENDOR = 'mockVendor'; + const MOCK_BRANCH = 'mockBranch'; /** @var Api */ private $api; @@ -44,7 +53,7 @@ protected function setUp() /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ /** - * @uses Potherca\Flysystem\Github\Api::exists + * */ final public function testApiShouldComplainWhenInstantiatedWithoutClient() { @@ -88,20 +97,16 @@ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForFileC $expected = self::MOCK_FILE_CONTENTS; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); $this->prepareMockApi( 'download', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference], + $api::API_REPOSITORY, + [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FILE_PATH, '/'), self::MOCK_REFERENCE], $expected ); @@ -119,20 +124,16 @@ final public function testApiShouldUseValuesFromSettingsWhenAskingClientIfFileEx $expected = self::MOCK_FILE_CONTENTS; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); $this->prepareMockApi( 'exists', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference], + $api::API_REPOSITORY, + [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FILE_PATH, '/'), self::MOCK_REFERENCE], $expected ); @@ -182,20 +183,16 @@ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForFileI $expected = self::MOCK_FILE_CONTENTS; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); $this->prepareMockApi( 'show', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference], + $api::API_REPOSITORY, + [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FILE_PATH, '/'), self::MOCK_REFERENCE], $expected ); @@ -229,30 +226,29 @@ final public function testApiShouldReturnMetadataForDirectoryWhenGivenPathIsDire { $api = $this->api; - $mockPackage = 'mockPackage'; - $mockPath = self::MOCK_FOLDER_PATH; - $mockReference = 'mockReference'; - $mockVendor = 'mockVendor'; + $mockPath = trim(self::MOCK_FOLDER_PATH, '/'); $expectedUrl = sprintf( '%s/repos/%s/%s/contents/%s?ref=%s', $api::GITHUB_API_URL, - $mockVendor, - $mockPackage, + self::MOCK_VENDOR, + self::MOCK_PACKAGE, $mockPath, - $mockReference + self::MOCK_REFERENCE ); $expectedHtmlUrl = sprintf( '%s/%s/%s/blob/%s/%s', $api::GITHUB_URL, - $mockVendor, - $mockPackage, - $mockReference, + self::MOCK_VENDOR, + self::MOCK_PACKAGE, + self::MOCK_REFERENCE, $mockPath ); $expected = [ + 'path' => $mockPath, + 'timestamp' => 1450252770, 'type' => $api::KEY_DIRECTORY, 'url' => $expectedUrl, 'html_url' => $expectedHtmlUrl, @@ -260,21 +256,52 @@ final public function testApiShouldReturnMetadataForDirectoryWhenGivenPathIsDire 'self' => $expectedUrl, 'html' => $expectedHtmlUrl, ), + 'mode' => '040000', + 'sha' => '30b7e362894eecb159ce0ba2921a8363cd297213', + 'name' => 'a-directory', + 'visibility' => 'public', + 'contents' => false, + 'stream' => false, ]; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); - $this->prepareMockApi( - 'show', - $api::API_REPO, - [$mockVendor, $mockPackage, $mockPath, $mockReference], - [0 => null] - ); + $recursiveTreesFixture = $this->loadFixture('repos/potherca-bot/test-repository/git/trees/HEAD?recursive=1'); + + $files = array_column($recursiveTreesFixture['tree'], $api::KEY_PATH); + + $this->addMocksToClient($this->mockClient, [ + Repo::class => [ + Commits::class => [ + 'method' => 'all', + 'exactly' => count($files), + 'with' => self::callback(function($vendor, $package, $context) use ($files) { + return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $context['sha'] === null + && preg_match(sprintf('#%s#', implode('|', $files)), $context['path']) === 1; + }), + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/commits'), + ], + Contents::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FOLDER_PATH, '/'), self::MOCK_REFERENCE], + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/contents/a-directory'), + ], + ], + GitData::class => [ + Trees::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, self::MOCK_REFERENCE, $api::RECURSIVE], + + 'willReturn' => $recursiveTreesFixture, + ], + ], + ]); $actual = $api->getMetaData($mockPath); @@ -322,41 +349,120 @@ final public function testApiShouldPassOnExceptionsWhenAskingInfoForFileCausesAn } /** - * @covers ::getTreeMetadata + * @covers ::getDirectoryContents * - * @uses Potherca\Flysystem\Github\Api::getCreatedTimestamp + * @dataProvider provideDirectoryContents + * + * @param array $data + */ + final public function testApiShouldRetrieveExpectedDirectoryContentsWhenAskedToGetDirectoryContents(array $data) + { + $api = $this->api; + + $this->prepareMockSettings([ + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, + ]); + + $recursiveTreesFixture = $this->loadFixture('repos/potherca-bot/test-repository/git/trees/HEAD?recursive=1'); + + $files = array_column($recursiveTreesFixture['tree'], $api::KEY_PATH); + + $this->addMocksToClient($this->mockClient, [ + Repo::class => [ + Commits::class => [ + 'method' => 'all', + 'exactly' => count($files), + 'with' => self::callback(function($vendor, $package, $context) use ($files) { + return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $context['sha'] === null + && preg_match(sprintf('#%s#', implode('|', $files)), $context['path']) === 1; + }), + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/commits'), + ], + ], + GitData::class => [ + Trees::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, self::MOCK_REFERENCE, $api::RECURSIVE], + 'willReturn' => $recursiveTreesFixture, + ], + ], + ]); + + $actual = $api->getDirectoryContents($data['path'], $data['recursive']); + + self::assertEquals($data['expected'], $actual); + } + + /** + * @covers ::getDirectoryContents * * @dataProvider provideExpectedMetadata * * @param array $data */ - final public function testApiShouldRetrieveExpectedMetadataWhenAskedToGetTreeMetadata($data) { + final public function testApiShouldRetrieveExpectedMetadataWhenAskedToGetMetadata(array $data) + { $api = $this->api; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); - $this->prepareMockApi( - 'show', - $api::API_GIT_DATA, - [$mockVendor, $mockPackage, $mockReference, true], - $this->getMockApiTreeResponse($data['truncated'], $api), - Trees::class - ); + $treesFixture = $this->loadFixture('repos/potherca-bot/test-repository/git/trees/HEAD?recursive=1'); + + $files = array_column($treesFixture['tree'], $api::KEY_PATH); + + $mockApis = [ + Repo::class => [ + Commits::class => [ + 'method' => 'all', + 'exactly' => count($files), + 'with' => self::callback(function($vendor, $package, $context) use ($files) { + return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $context['sha'] === null + && preg_match(sprintf('#%s#', implode('|', $files)), $context['path']) === 1; + }), + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/commits'), + ], + Contents::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => self::callback(function ($vendor, $package, $path, $reference) use ($files) { + return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $reference === self::MOCK_REFERENCE + && preg_match(sprintf('#%s#', implode('|', $files)), $path) === 1; + }), + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/contents/a-directory'), + ], + ], + GitData::class => [ + Trees::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, self::MOCK_REFERENCE, $api::RECURSIVE], + 'willReturn' => $treesFixture, + ], + ], + ]; + +// if ($data['count'] !== 0) { +// $mockApis[Repo::class][Commits::class] = [ +// 'method' => 'all', +// 'exactly' => $data['count'], +// 'with' => self::callback(function ($vendor, $package, $context) use ($files) { +// return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $context['sha'] === null +// && preg_match(sprintf('#%s#', implode('|', $files)), $context['path']) === 1; +// }), +// 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/commits'), +// ]; +// } - $actual = $api->getTreeMetadata($data['path'], $data['recursive']); + $this->addMocksToClient($this->mockClient, $mockApis); - $actual = array_map(function ($value) { - $value['timestamp'] = null; - return $value; - }, $actual); + $actual = $api->getMetaData($data['path']); self::assertEquals($data['expected'], $actual); } @@ -365,8 +471,6 @@ final public function testApiShouldRetrieveExpectedMetadataWhenAskedToGetTreeMet * @covers ::guessMimeType * * @uses League\Flysystem\Util\MimeType - * @uses Potherca\Flysystem\Github\Api::getFileContents - * @uses Potherca\Flysystem\Github\Api::getMetaData */ final public function testApiShouldUseFileContentsToGuessMimeTypeWhenExtensionUnavailable() { @@ -374,14 +478,10 @@ final public function testApiShouldUseFileContentsToGuessMimeTypeWhenExtensionUn $expected = 'image/png'; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); $image = imagecreatetruecolor(1,1); @@ -393,8 +493,8 @@ final public function testApiShouldUseFileContentsToGuessMimeTypeWhenExtensionUn $this->prepareMockApi( 'download', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference], + $api::API_REPOSITORY, + [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FILE_PATH, '/'), self::MOCK_REFERENCE], $contents ); @@ -407,8 +507,6 @@ final public function testApiShouldUseFileContentsToGuessMimeTypeWhenExtensionUn * @covers ::guessMimeType * * @uses League\Flysystem\Util\MimeType - * @uses Potherca\Flysystem\Github\Api::getFileContents - * @uses Potherca\Flysystem\Github\Api::getMetaData */ final public function testApiShouldGuessMimeTypeCorrectlyWhenGivenPathIsDirectory() { @@ -416,22 +514,42 @@ final public function testApiShouldGuessMimeTypeCorrectlyWhenGivenPathIsDirector $expected = $api::MIME_TYPE_DIRECTORY; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, ]); - $this->prepareMockApi( - 'show', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FOLDER_PATH, $mockReference], - [0 => [$api::KEY_TYPE => $api::MIME_TYPE_DIRECTORY]] - ); + $treesFixture = $this->loadFixture('repos/potherca-bot/test-repository/git/trees/HEAD'); + $files = array_column($treesFixture['tree'], $api::KEY_PATH); + + $this->addMocksToClient($this->mockClient, [ + Repo::class => [ + Commits::class => [ + 'method' => 'all', + 'exactly' => count($files), + 'with' => self::callback(function($vendor, $package, $context) use ($files) { + return $vendor === self::MOCK_VENDOR && $package === self::MOCK_PACKAGE && $context['sha'] === null + && preg_match(sprintf('#%s#', implode('|', $files)), $context['path']) === 1; + }), + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/commits'), + ], + Contents::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FOLDER_PATH, '/'), self::MOCK_REFERENCE], + 'willReturn' => $this->loadFixture('repos/potherca-bot/test-repository/contents/a-directory'), + ], + ], + GitData::class => [ + Trees::class => [ + 'method' => 'show', + 'exactly' => 1, + 'with' => [self::MOCK_VENDOR, self::MOCK_PACKAGE, self::MOCK_REFERENCE, $api::RECURSIVE], + 'willReturn' => $treesFixture, + ], + ], + ]); $actual = $api->guessMimeType(self::MOCK_FOLDER_PATH); @@ -439,27 +557,23 @@ final public function testApiShouldGuessMimeTypeCorrectlyWhenGivenPathIsDirector } /** - * @uses Potherca\Flysystem\Github\Api::exists + * */ final public function testApiShouldUseCredentialsWhenTheyHaveBeenGiven() { $api = $this->api; - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockReference = 'reference'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getReference' => $mockReference, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getReference' => self::MOCK_REFERENCE, 'getCredentials' => ['foo'] ]); $this->prepareMockApi( 'exists', - $api::API_REPO, - [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference], + $api::API_REPOSITORY, + [self::MOCK_VENDOR, self::MOCK_PACKAGE, trim(self::MOCK_FILE_PATH, '/'), self::MOCK_REFERENCE], '' ); @@ -490,25 +604,83 @@ private function getMockSettings() ->getMock(); } + private function addMocksToClient(MockObject $mockClient, array $apiCollection) + { + $parentApiCollection = []; + + foreach ($apiCollection as $parentApiClass => $children) { + + $mockParentApi = $this->getMockBuilder($parentApiClass) + ->disableOriginalConstructor() + ->getMock() + ; + + foreach ($children as $childApiClass => $properties) { + $parts = explode('\\', $childApiClass); + $childApiName = strtolower(array_pop($parts)); + + $mockChildApi = $this->getMockBuilder($childApiClass) + ->disableOriginalConstructor() + ->getMock() + ; + + $returnMethod = 'willReturn'; + if (is_callable($properties['willReturn'])) { + $returnMethod = 'willReturnCallback'; + } + + $mockChildApi->expects(self::exactly($properties['exactly'])) + ->method($properties['method']) + ->withConsecutive($properties['with']) + ->{$returnMethod}($properties['willReturn']) + ; + + $mockParentApi->expects(self::exactly(1)) + ->method($childApiName) + ->with() + ->willReturn($mockChildApi) + ; + } + + $parts = explode('\\', $parentApiClass); + $parentApiName = strtolower(array_pop($parts)); + $parentApiCollection[$parentApiName] = $mockParentApi; + } + + $parentApiCount = count($parentApiCollection); + $parentApiNames = array_keys($parentApiCollection); + $parentApiNamesPattern = implode('|', $parentApiNames); + + $mockClient->expects(self::exactly($parentApiCount)) + ->method('api') + ->with(self::matchesRegularExpression(sprintf('/%s/i', $parentApiNamesPattern))) + ->willReturnCallback(function ($apiName) use ($parentApiCollection) { + return $parentApiCollection[strtolower($apiName)]; + }) + ; + } + /** + * @deprecated Use `addMocksToClient` instead. + * * @param string $method * @param string $apiName * @param array $apiParameters * @param mixed $apiOutput - * @param string $repositoryClass + * @param string $childApiClass */ private function prepareMockApi( $method, $apiName, $apiParameters, $apiOutput, - $repositoryClass = Contents::class + $childApiClass = Contents::class ) { - $parts = explode('\\', $repositoryClass); - $repositoryName = strtolower(array_pop($parts)); + $parts = explode('\\', $childApiClass); + $childApiName = strtolower(array_pop($parts)); - $methods = [$repositoryName, 'getPerPage', 'setPerPage']; + $methods = [$childApiName, 'getPerPage', 'setPerPage']; $shouldMockCommitsRepository = false; if (in_array('commits', $methods, true) === false) { @@ -516,17 +688,17 @@ private function prepareMockApi( $methods[] = 'commits'; } - $mockApi = $this->getMockBuilder(ApiInterface::class) + $mockParentApi = $this->getMockBuilder(ApiInterface::class) ->setMethods($methods) ->getMock() ; - $mockRepository = $this->getMockBuilder($repositoryClass) + $mockChildApi = $this->getMockBuilder($childApiClass) ->disableOriginalConstructor() ->getMock() ; - $mockRepository->expects(self::exactly(1)) + $mockChildApi->expects(self::exactly(1)) ->method($method) ->withAnyParameters() ->willReturnCallback(function () use ($apiParameters, $apiOutput) { @@ -535,13 +707,13 @@ private function prepareMockApi( }) ; - $mockApi->expects(self::exactly(1)) - ->method($repositoryName) - ->willReturn($mockRepository) + $mockParentApi->expects(self::exactly(1)) + ->method($childApiName) + ->willReturn($mockChildApi) ; if ($shouldMockCommitsRepository === true) { - $mockCommitsRepository = $this->getMockBuilder(Commits::class) + $mockCommitsApi = $this->getMockBuilder(Commits::class) ->disableOriginalConstructor() ->getMock() ; @@ -551,21 +723,21 @@ private function prepareMockApi( ['commit' => ['committer' => ['date' => '20140202']]] ]; - $mockCommitsRepository->expects(self::any()) + $mockCommitsApi->expects(self::any()) ->method('all') ->withAnyParameters() ->willReturn($apiOutput) ; - $mockApi->expects(self::any()) + $mockParentApi->expects(self::any()) ->method('commits') - ->willReturn($mockCommitsRepository) + ->willReturn($mockCommitsApi) ; } $this->mockClient->expects(self::any()) ->method('api') ->with(self::matchesRegularExpression(sprintf('/%s|repo/', $apiName))) - ->willReturn($mockApi) + ->willReturn($mockParentApi) ; } @@ -582,66 +754,22 @@ private function prepareMockSettings(array $expectations) } } - /** - * @param bool $truncated - * @param Api $api - * @return array - */ - private function getMockApiTreeResponse($truncated, Api $api) - { - return [ - $api::KEY_TREE => [ - [ - 'path' => self::MOCK_FILE_PATH, - 'mode' => '100644', - 'type' => 'tree', - 'size' => 57, - ], - [ - 'path' => self::MOCK_FILE_PATH . 'Foo', - 'basename' => self::MOCK_FILE_PATH . 'Foo', - 'mode' => '100644', - 'type' => 'blob', - 'size' => 57, - ], - [ - 'path' => self::MOCK_FILE_PATH . '/Bar', - 'name' => self::MOCK_FILE_PATH . '/Bar', - 'mode' => '100644', - 'type' => 'blob', - 'size' => 57, - ], - [ - 'path' => 'some/other/file', - 'mode' => '100644', - 'type' => 'blob', - 'size' => 747, - ], - ], - 'truncated' => $truncated, - ]; - } - private function prepareFixturesForTimeStamp() { date_default_timezone_set('UTC'); - $mockVendor = 'vendor'; - $mockPackage = 'package'; - $mockBranch = 'branch'; - $this->prepareMockSettings([ - 'getVendor' => $mockVendor, - 'getPackage' => $mockPackage, - 'getBranch' => $mockBranch, + 'getVendor' => self::MOCK_VENDOR, + 'getPackage' => self::MOCK_PACKAGE, + 'getBranch' => self::MOCK_BRANCH, ]); $apiParameters = [ - $mockVendor, - $mockPackage, + self::MOCK_VENDOR, + self::MOCK_PACKAGE, [ - 'sha' => $mockBranch, - 'path' => self::MOCK_FILE_PATH + 'sha' => self::MOCK_BRANCH, + 'path' => trim(self::MOCK_FILE_PATH, '/') ] ]; @@ -654,7 +782,7 @@ private function prepareFixturesForTimeStamp() $this->prepareMockApi( 'all', - Api::API_REPO, + Api::API_REPOSITORY, $apiParameters, $apiOutput, Commits::class @@ -668,274 +796,195 @@ private function prepareFixturesForTimeStamp() final public function provideExpectedMetadata() { return [ - 'Filepath, not recursive, not truncated' => [[ + 'FilePath' => [[ + 'count' => 0, 'path' => self::MOCK_FILE_PATH, 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', + 'path' => 'a-directory/another-file.js', + 'mode' => '100755', 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/file/Bar', - 'name' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, + 'sha' => 'f542363e1b45aa7a33e5e731678dee18f7a1e729', + 'size' => 52, + 'url' => 'https://api.github.com/repos/mockVendor/mockPackage/contents/a-directory/another-file.js?ref=mockReference', + 'name' => 'a-directory/another-file.js', 'visibility' => 'public', 'contents' => false, 'stream' => false, - 'timestamp' => null + 'timestamp' => 1450252770, + 'html_url' => 'https://github.com/mockVendor/mockPackage/blob/mockReference/a-directory/another-file.js', + '_links' => [ + 'self' => 'https://api.github.com/repos/mockVendor/mockPackage/contents/a-directory/another-file.js?ref=mockReference', + 'html' => 'https://github.com/mockVendor/mockPackage/blob/mockReference/a-directory/another-file.js', ], ], - 'recursive' => false, - 'truncated' => false, ]], - 'Filepath, recursive, not truncated' => [[ - 'path' => self::MOCK_FILE_PATH, + 'DirectoryPath' => [[ + 'count' => 2, + 'path' => self::MOCK_FOLDER_PATH, 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', + 'path' => 'a-directory', + 'mode' => '040000', 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', + 'sha' => '30b7e362894eecb159ce0ba2921a8363cd297213', + 'url' => 'https://api.github.com/repos/mockVendor/mockPackage/contents/a-directory?ref=mockReference', + 'name' => 'a-directory', + 'visibility' => 'public', 'contents' => false, 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' + 'timestamp' => 1450252770, + 'html_url' => 'https://github.com/mockVendor/mockPackage/blob/mockReference/a-directory', + '_links' => [ + 'self' => 'https://api.github.com/repos/mockVendor/mockPackage/contents/a-directory?ref=mockReference', + 'html' => 'https://github.com/mockVendor/mockPackage/blob/mockReference/a-directory', ], - [ - 'path' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/file/Bar', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ] ], + ]], + ]; + } + + final public function provideDirectoryContents() + { + $subDirectoryContent = [ + [ + 'path' => 'a-directory/another-file.js', + 'mode' => '100755', + 'type' => 'file', + 'size' => 52, + 'name' => 'a-directory/another-file.js', + 'contents' => false, + 'stream' => false, + 'timestamp' => 1450252770, + 'visibility' => 'public', + 'sha' => 'f542363e1b45aa7a33e5e731678dee18f7a1e729', + 'url' => 'https://api.github.com/repos/potherca-bot/test-repository/git/blobs/f542363e1b45aa7a33e5e731678dee18f7a1e729', + ], + [ + 'path' => 'a-directory/readme.txt', + 'mode' => '100755', + 'type' => 'file', + 'size' => 31, + 'name' => 'a-directory/readme.txt', + 'contents' => false, + 'stream' => false, + 'timestamp' => 1450252770, + 'visibility' => 'public', + 'sha' => '27f8ec8435cb07992ecf18f9d5494ffc14948368', + 'url' => 'https://api.github.com/repos/potherca-bot/test-repository/git/blobs/27f8ec8435cb07992ecf18f9d5494ffc14948368', + ], + ]; + + $subDirectory = [ + 'path' => 'a-directory', + 'mode' => '040000', + 'type' => 'dir', + 'name' => 'a-directory', + 'contents' => false, + 'stream' => false, + 'timestamp' => 1450252770, + 'visibility' => 'public', + 'sha' => '30b7e362894eecb159ce0ba2921a8363cd297213', + 'url' => 'https://api.github.com/repos/potherca-bot/test-repository/git/trees/30b7e362894eecb159ce0ba2921a8363cd297213', + ]; + + $nonRecursiveRepositoryContent = [ + [ + 'path' => 'README', + 'mode' => '100755', + 'type' => 'file', + 'size' => 58, + 'name' => 'README', + 'contents' => false, + 'stream' => false, + 'timestamp' => 1450252770, + 'visibility' => 'public', + 'sha' => '1ff3a296caf2d27828dd8c40673c88dbf99d4b3a', + 'url' => 'https://api.github.com/repos/potherca-bot/test-repository/git/blobs/1ff3a296caf2d27828dd8c40673c88dbf99d4b3a', + ], + $subDirectory, + [ + 'path' => 'a-file.php', + 'mode' => '100755', + 'type' => 'file', + 'sha' => 'c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1', + 'size' => 117, + 'url' => 'https://api.github.com/repos/potherca-bot/test-repository/git/blobs/c6e6cd91e3ae40ab74883720a0d6cfb2af89e4b1', + 'name' => 'a-file.php', + 'visibility' => 'public', + 'contents' => false, + 'stream' => false, + 'timestamp' => 1450252770, + ], + ]; + + $recursiveRepositoryContent = array_merge($nonRecursiveRepositoryContent, $subDirectoryContent); + + return [ + // @TODO: Add Directory path? + 'Directorypath, not recursive, not truncated' => [[ + //@TODO: Add sub-sub-directory so non-recursive directory contents can be properly tested + 'path' => self::MOCK_FOLDER_PATH, + 'expected' => array_merge([$subDirectory], $subDirectoryContent), + 'recursive' => false, + 'truncated' => false, + ]], + 'Directorypath, recursive, not truncated' => [[ + 'path' => self::MOCK_FOLDER_PATH, + 'expected' => array_merge([$subDirectory], $subDirectoryContent), 'recursive' => true, 'truncated' => false, ]], - 'Filepath, not recursive, truncated' => [[ - 'path' => self::MOCK_FILE_PATH, - 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', - 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/file/Bar', - 'name' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'visibility' => 'public', - 'contents' => false, - 'stream' => false, - 'timestamp' => null - ], - ], + 'No FilePath, not recursive, not truncated' => [[ + 'path' => '', + 'expected' => $nonRecursiveRepositoryContent, 'recursive' => false, - 'truncated' => true, + 'truncated' => false, ]], - 'Filepath, recursive, truncated' => [[ - 'path' => self::MOCK_FILE_PATH, - 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', - 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/file/Bar', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ] - ], - 'recursive' => true, + 'No FilePath, not recursive, truncated' => [[ + 'path' => '', + 'expected' => $nonRecursiveRepositoryContent, + 'recursive' => false, 'truncated' => true, ]], - 'No Filepath, recursive, not truncated' => [[ + 'No FilePath, recursive, not truncated' => [[ 'path' => '', - 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', - 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/file/Bar', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => 'some/other/file', - 'mode' => '100644', - 'type' => 'file', - 'size' => 747, - 'name' => 'some/other/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ] - ], + 'expected' => $recursiveRepositoryContent, 'recursive' => true, 'truncated' => false, ]], - 'No Filepath, recursive, truncated' => [[ + 'No FilePath, recursive, truncated' => [[ 'path' => '', - 'expected' => [ - [ - 'path' => '/path/to/mock/file', - 'mode' => '100644', - 'type' => 'dir', - 'size' => 57, - 'name' => '/path/to/mock/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/fileFoo', - 'basename' => '/path/to/mock/fileFoo', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/fileFoo', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => '/path/to/mock/file/Bar', - 'mode' => '100644', - 'type' => 'file', - 'size' => 57, - 'name' => '/path/to/mock/file/Bar', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ], - [ - 'path' => 'some/other/file', - 'mode' => '100644', - 'type' => 'file', - 'size' => 747, - 'name' => 'some/other/file', - 'contents' => false, - 'stream' => false, - 'timestamp' => null, - 'visibility' => 'public' - ] - ], + 'expected' => $recursiveRepositoryContent, 'recursive' => true, 'truncated' => true, ]], ]; } + + /** + * @param $fixtureName + * @return mixed + */ + private function loadFixture($fixtureName) + { + $fixtureName = urlencode($fixtureName); + + $fixtureDirectory = sprintf('%s/fixtures', dirname(__DIR__)); + $fixturePath = sprintf('%s/%s.json', $fixtureDirectory, $fixtureName); + + if (is_file($fixturePath) === false) { + self::fail( + sprintf('Could not find file for fixture "%s"', $fixtureName) + ); + } else { + $fixture = json_decode(file_get_contents($fixturePath), true); + $lastJsonError = json_last_error(); + if ($lastJsonError !== JSON_ERROR_NONE) { + self::fail( + sprintf('Error Reading Fixture "%s": %s', $fixturePath, json_last_error_msg()) + ); + } + } + + return $fixture; + } } diff --git a/tests/unit-tests/GithubAdapterTest.php b/tests/unit-tests/GithubAdapterTest.php index afd1d72..3a2bfa2 100644 --- a/tests/unit-tests/GithubAdapterTest.php +++ b/tests/unit-tests/GithubAdapterTest.php @@ -35,6 +35,11 @@ protected function setup() /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ /** + * @param $method + * @param $apiMethod + * @param $parameters + * @param mixed $returnValue + * * @covers ::has * @covers ::read * @covers ::listContents @@ -45,21 +50,19 @@ protected function setup() * @covers ::getVisibility * * @dataProvider provideReadMethods - * - * @param $method - * @param $apiMethod - * @param $parameters - * @param mixed $returnValue */ - final public function testAdapterShouldPassParameterToClient($method, $apiMethod, $parameters, $returnValue = null) - { + final public function testAdapterShouldPassParameterToClientWhenReadMethodsAreCalled( + $method, + $apiMethod, + $parameters, + $returnValue = null + ) { if (is_string($returnValue) && is_file(sprintf('%s/../fixtures/%s.json', __DIR__, $returnValue))) { $fixturePath = sprintf('%s/../fixtures/%s.json', __DIR__, $returnValue); $fixture = json_decode(file_get_contents($fixturePath), true); $returnValue = $fixture['tree']; } - $mocker = $this->mockClient->expects(self::exactly(1)) ->method($apiMethod) ->willReturn($returnValue) @@ -78,15 +81,16 @@ final public function provideReadMethods() return [ 'has' => ['has', 'exists', [self::MOCK_FILE_PATH]], 'read' => ['read', 'getFileContents', [self::MOCK_FILE_PATH]], - 'listContents - File' => ['listContents', 'getTreeMetadata', [self::MOCK_FILE_PATH, false]], - 'listContents - File - recursive' => ['listContents', 'getTreeMetadata', [self::MOCK_FILE_PATH, true]], - 'listContents - Folder' => ['listContents', 'getTreeMetadata', [self::MOCK_FOLDER_PATH, false], ''], - 'listContents - Folder - recursive' => ['listContents', 'getTreeMetadata', [self::MOCK_FOLDER_PATH, true], 'listContents-folder-recursive'], - 'getMetadata' => ['getMetadata', 'getMetadata', [self::MOCK_FILE_PATH]], + 'listContents - File' => ['listContents', 'getDirectoryContents', [self::MOCK_FILE_PATH, false]], + 'listContents - File - recursive' => ['listContents', 'getDirectoryContents', [self::MOCK_FILE_PATH, true]], + 'listContents - Folder' => ['listContents', 'getDirectoryContents', [self::MOCK_FOLDER_PATH, false], ''], + 'listContents - Folder - recursive' => ['listContents', 'getDirectoryContents', [self::MOCK_FOLDER_PATH, true], 'repos%2Fpotherca-bot%2Ftest-repository%2Fgit%2Ftrees%2FHEAD'], + 'getMetadata - File' => ['getMetadata', 'getMetadata', [self::MOCK_FILE_PATH]], + 'getMetadata - Folder' => ['getMetadata', 'getMetadata', [self::MOCK_FOLDER_PATH]], 'getSize' => ['getSize', 'getMetadata', [self::MOCK_FILE_PATH]], 'getMimetype' => ['getMimetype', 'guessMimeType', [self::MOCK_FILE_PATH]], 'getTimestamp' => ['getTimestamp', 'getLastUpdatedTimestamp', [self::MOCK_FILE_PATH]], - 'getVisibility' => ['getVisibility', 'getTreeMetadata', [self::MOCK_FILE_PATH]], + 'getVisibility' => ['getVisibility', 'getDirectoryContents', [self::MOCK_FILE_PATH]], ]; } }