diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..0d76554
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,9 @@
+* text=auto
+
+/tests export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/.travis.yml export-ignore
+/phpunit.xml.dist export-ignore
+/CHANGELOG.md export-ignore
+/README.md export-ignore
diff --git a/.gitignore b/.gitignore
index 82405c2..bfcf449 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@ build
vendor
composer.lock
-phpunit.xml
\ No newline at end of file
+phpunit.xml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..093a889
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,23 @@
+language: php
+
+php:
+ - 5.5
+ - 5.6
+ - 7.0
+ - hhvm
+
+sudo: false
+
+matrix:
+ allow_failures:
+ - php: 7.0
+ - php: hhvm
+
+install:
+ - composer install
+
+script:
+ - vendor/bin/phpunit --configuration build/phpunit.xml && cat build/testdox.txt build/coverage.txt
+
+after_script:
+ - php vendor/bin/coveralls -v
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 576590e..c386634 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,42 @@
# Change Log
All notable changes to the `flysystem-github` project will be documented in this
-file. This project adheres to [Semantic Versioning](http://semver.org/).
+file. This project adheres to the [keep-a-changelog](http://keepachangelog.com/)
+and [Semantic Versioning](http://semver.org/) conventions.
-## 0.1.0 - 2015-07-18 - Read functionality
+
+
+## v0.2.0 - 2015-07-21 - Improvements and UnitTests
+
+### Added
+- Adds automated checks (a.k.a. unit-tests) for the Adapter, Client and Settings classes.
+- Adds various utility files for Travis builds, Coveralls and Composer
+
+### Changed
+- Makes the PHPUnit configuration more strict
+- Renames the Client class to "Api"
+
+## v0.1.0 - 2015-07-18 - Read functionality
+
+### Added
+- Read functionality and Github API authentication have been implemented.
+
+## v0.0.0 - 2015-05-11 - Project Setup
-## 0.0.0 - 2015-05-11 - Project Setup
### Added
-Set up project basics like .gitignore file, PHPUnit Configuration file,
+- Set up project basics like .gitignore file, PHPUnit Configuration file,
Contributing guidelines, Composer file stating dependencies, MIT License, README
file and this CHANGELOG file.
-[unreleased]: https://github.com/potherca/flystystem-github/compare/v0.1.0...HEAD
+[unreleased]: https://github.com/potherca/flystystem-github/compare/v0.2.0...HEAD
+[0.2.0]: https://github.com/potherca/flystystem-github/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/potherca/flystystem-github/compare/v0.0.0...v0.1.0
+[keep-a-changelog]: http://keepachangelog.com/
+[Semantic Versioning]: http://semver.org/
diff --git a/README.md b/README.md
index bc81427..854f5c7 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,11 @@
[![Latest Version](https://img.shields.io/github/release/potherca/flysystem-github.svg?style=flat-square)](https://github.com/potherca/flysystem-github/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
-[![Build Status](https://img.shields.io/travis/potherca/flysystem-github/master.svg?style=flat-square)](https://travis-ci.org/potherca/flysystem-github)
-[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/potherca/flysystem-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/potherca/flysystem-github/code-structure)
+[![Build Status](https://img.shields.io/travis/potherca/flysystem-github.svg?style=flat-square)](https://travis-ci.org/potherca/flysystem-github)
+[![Coverage Status](https://coveralls.io/repos/potherca/flysystem-github/badge.svg)](https://coveralls.io/github/potherca/flysystem-github)
[![Quality Score](https://img.shields.io/scrutinizer/g/potherca/flysystem-github.svg?style=flat-square)](https://scrutinizer-ci.com/g/potherca/flysystem-github)
[![Total Downloads](https://img.shields.io/packagist/dt/potherca/flysystem-github.svg?style=flat-square)](https://packagist.org/packages/potherca/flysystem-github)
-
## Install
Via Composer
@@ -28,9 +27,9 @@ limit.
### Basic Usage
```php
-use Github\Client as GithubClient;
+use Github\Client;
use League\Flysystem\Filesystem;
-use Potherca\Flysystem\Github\Client;
+use Potherca\Flysystem\Github\Api;
use Potherca\Flysystem\Github\GithubAdapter;
use Potherca\Flysystem\Github\Settings;
@@ -38,17 +37,17 @@ $project = 'thephpleague/flysystem';
$settings = new Settings($project);
-$client = new Client(new GithubClient(), $settings);
-$adapter = new GithubAdapter($client);
+$api = new Api(new Client(), $settings);
+$adapter = new GithubAdapter($api);
$filesystem = new Filesystem($adapter);
```
### Authentication
```php
-use Github\Client as GithubClient;
+use Github\Client;
use League\Flysystem\Filesystem;
-use Potherca\Flysystem\Github\Client;
+use Potherca\Flysystem\Github\Api;
use Potherca\Flysystem\Github\GithubAdapter;
use Potherca\Flysystem\Github\Settings;
@@ -58,19 +57,19 @@ $credentials = [Settings::AUTHENTICATE_USING_TOKEN, '83347e315b8bb4790a48ed6953a
$settings = new Settings($project, $credentials);
-$client = new Client(new GithubClient(), $settings);
-$adapter = new GithubAdapter($client);
+$api = new Api(new Client(), $settings);
+$adapter = new GithubAdapter($api);
$filesystem = new Filesystem($adapter);
```
### Cache Usage
```php
-use Github\Client as GithubClient;
+use Github\Client;
use Github\HttpClient\CachedHttpClient as CachedClient;
use Github\HttpClient\Cache\FilesystemCache as Cache;
use League\Flysystem\Filesystem;
-use Potherca\Flysystem\Github\Client;
+use Potherca\Flysystem\Github\Api;
use Potherca\Flysystem\Github\GithubAdapter;
use Potherca\Flysystem\Github\Settings;
@@ -82,8 +81,8 @@ $cache = new Cache('/tmp/github-api-cache')
$cacheClient = new CachedClient();
$cacheClient->setCache($cache);
-$client = new Client($cacheClient, $settings);
-$adapter = new GithubAdapter($client);
+$api = new Api($cacheClient, $settings);
+$adapter = new GithubAdapter($api);
$filesystem = new Filesystem($adapter);
```
@@ -94,13 +93,17 @@ $filesystem = new Filesystem($adapter);
$ composer test
```
+## Security
+
+If you discover any security related issues, please email potherca@gmail.com instead of using the issue tracker.
+
## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
-## Security
+## Change Log
-If you discover any security related issues, please email potherca@gmail.com instead of using the issue tracker.
+Please see [CHANGELOG](CHANGELOG.md) for details.
## Credits
diff --git a/build/phpunit.xml b/build/phpunit.xml
new file mode 100644
index 0000000..df9f636
--- /dev/null
+++ b/build/phpunit.xml
@@ -0,0 +1,35 @@
+
+
+
+
+ ../tests
+
+
+
+
+ src/
+
+
+
+
+
+
+
+
+
+
diff --git a/composer.json b/composer.json
index 8e0aa82..bc1dfeb 100644
--- a/composer.json
+++ b/composer.json
@@ -20,13 +20,15 @@
}
],
"require": {
- "php" : ">=5.3.0",
+ "php" : ">=5.5",
"knplabs/github-api": "^1.4",
"league/flysystem": "^1.0"
},
"require-dev": {
- "phpunit/phpunit" : "4.*",
- "scrutinizer/ocular": "~1.1"
+ "phpunit/phpunit" : "^4.7.7",
+ "satooshi/php-coveralls": "^0.6.1",
+ "scrutinizer/ocular": "^1.1",
+ "whatthejeff/nyancat-phpunit-resultprinter": "^1.2"
},
"autoload": {
"psr-4": {
@@ -40,7 +42,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "1.0-dev"
+ "dev-master": "0.2.0-dev"
}
},
"scripts": {
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 68bbdfd..9b8dda9 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,16 +1,25 @@
-
+
-
+
tests
@@ -20,10 +29,9 @@
-
-
-
-
-
+
+
+
+
diff --git a/src/Api.php b/src/Api.php
new file mode 100644
index 0000000..c5fbbef
--- /dev/null
+++ b/src/Api.php
@@ -0,0 +1,398 @@
+authenticate();
+ return $this->client->api($name);
+ }
+
+ /**
+ * @return GitData
+ */
+ private function getGitDataApi()
+ {
+ return $this->getApi(self::API_GIT_DATA);
+ }
+
+ /**
+ * @return Repo
+ */
+ private function getRepositoryApi()
+ {
+ return $this->getApi(self::API_REPO);
+ }
+
+ /**
+ * @return \Github\Api\Repository\Contents
+ */
+ private function getRepositoryContent()
+ {
+ return $this->getRepositoryApi()->contents();
+ }
+
+ //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ final public function __construct(Client $client, SettingsInterface $settings)
+ {
+ /* @NOTE: If $settings contains `credentials` but not an `author` we are
+ * still in `read-only` mode.
+ */
+
+ $this->client = $client;
+ $this->settings = $settings;
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return bool
+ */
+ final public function exists($path)
+ {
+ return $this->getRepositoryContent()->exists(
+ $this->settings->getVendor(),
+ $this->settings->getPackage(),
+ $path,
+ $this->settings->getReference()
+ );
+ }
+
+ /**
+ * @param $path
+ *
+ * @return null|string
+ *
+ * @throws \Github\Exception\ErrorException
+ */
+ final public function getFileContents($path)
+ {
+ return $this->getRepositoryContent()->download(
+ $this->settings->getVendor(),
+ $this->settings->getPackage(),
+ $path,
+ $this->settings->getReference()
+ );
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return array
+ */
+ final public function getLastUpdatedTimestamp($path)
+ {
+ $commits = $this->commitsForFile($path);
+
+ $updated = array_shift($commits);
+
+ $time = new \DateTime($updated['commit']['committer']['date']);
+
+ return ['timestamp' => $time->getTimestamp()];
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return array
+ */
+ final public function getCreatedTimestamp($path)
+ {
+ $commits = $this->commitsForFile($path);
+
+ $created = array_pop($commits);
+
+ $time = new \DateTime($created['commit']['committer']['date']);
+
+ return ['timestamp' => $time->getTimestamp()];
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return array|bool
+ */
+ 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;
+ }
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param string $path
+ * @param bool $recursive
+ *
+ * @return array
+ */
+ final public function getRecursiveMetadata($path, $recursive)
+ {
+ // If $info['truncated'] is `true`, the number of items in the tree array
+ // exceeded the github maximum limit. If you need to fetch more items,
+ // multiple calls will be needed
+
+ $info = $this->getGitDataApi()->trees()->show(
+ $this->settings->getVendor(),
+ $this->settings->getPackage(),
+ $this->settings->getReference(),
+ $recursive
+ );
+
+ $treeMetadata = $this->extractMetaDataFromTreeInfo($info[self::KEY_TREE], $path, $recursive);
+
+ return $this->normalizeTreeMetadata($treeMetadata);
+ }
+
+ /**
+ * @param string $path
+ *
+ * @return null|string
+ */
+ final public function guessMimeType($path)
+ {
+ //@NOTE: The github API does not return a MIME type, so we have to guess :-(
+ if (strrpos($path, '.') > 1) {
+ $extension = substr($path, strrpos($path, '.')+1);
+ $mimeType = MimeType::detectByFileExtension($extension) ?: 'text/plain';
+ } else {
+ $content = $this->getFileContents($path);
+ $mimeType = MimeType::detectByContent($content);
+ }
+
+ return $mimeType;
+ }
+
+ ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ *
+ */
+ private function authenticate()
+ {
+ if ($this->isAuthenticationAttempted === false) {
+ $credentials = $this->settings->getCredentials();
+
+ if (empty($credentials) === false) {
+ $credentials = array_replace(
+ [null, null, null],
+ $credentials
+ );
+
+ $this->client->authenticate(
+ $credentials[1],
+ $credentials[2],
+ $credentials[0]
+ );
+ }
+ $this->isAuthenticationAttempted = true;
+ }
+ }
+
+ /**
+ * @param array $tree
+ * @param string $path
+ * @param bool $recursive
+ *
+ * @return array
+ */
+ private function extractMetaDataFromTreeInfo(array $tree, $path, $recursive)
+ {
+ if(empty($path) === false) {
+ $metadata = array_filter($tree, function ($entry) use ($path, $recursive) {
+ $match = false;
+
+ if (strpos($entry[self::KEY_PATH], $path) === 0) {
+ if ($recursive === true) {
+ $match = true;
+ } else {
+ $length = strlen($path);
+ $match = (strpos($entry[self::KEY_PATH], '/', $length) === false);
+ }
+ }
+
+ return $match;
+ });
+ } elseif ($recursive === false) {
+ $metadata = array_filter($tree, function ($entry) use ($path) {
+ return (strpos($entry[self::KEY_PATH], '/', strlen($path)) === false);
+ });
+ } else {
+ $metadata = $tree;
+ }
+
+ return $metadata;
+ }
+
+ /**
+ * @param $permissions
+ * @return string
+ */
+ private function guessVisibility($permissions)
+ {
+ return $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
+ }
+
+ /**
+ * @param array $metadata
+ *
+ * @return array
+ */
+ private function normalizeTreeMetadata($metadata)
+ {
+ $result = [];
+
+ if (is_array(current($metadata)) === false) {
+ $metadata = [$metadata];
+ }
+
+ foreach ($metadata as $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 $result;
+ }
+
+ /**
+ * @param $path
+ *
+ * @return array
+ */
+ private function commitsForFile($path)
+ {
+ return $this->getRepositoryApi()->commits()->all(
+ $this->settings->getVendor(),
+ $this->settings->getPackage(),
+ array(
+ 'sha' => $this->settings->getBranch(),
+ 'path' => $path
+ )
+ );
+ }
+
+ /**
+ * @param array $entry
+ * @param string $key
+ * @param bool $default
+ *
+ * @return mixed
+ */
+ private function setDefaultValue(array &$entry, $key, $default = false)
+ {
+ if (isset($entry[$key]) === false) {
+ $entry[$key] = $default;
+ }
+ }
+
+ /**
+ * @param $entry
+ */
+ private function setEntryType(&$entry)
+ {
+ if (isset($entry[self::KEY_TYPE]) === true) {
+ switch ($entry[self::KEY_TYPE]) {
+ case self::KEY_BLOB:
+ $entry[self::KEY_TYPE] = self::KEY_FILE;
+ break;
+
+ case self::KEY_TREE:
+ $entry[self::KEY_TYPE] = self::KEY_DIRECTORY;
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param $entry
+ */
+ private function setEntryVisibility(&$entry)
+ {
+ if (isset($entry[self::KEY_MODE])) {
+ $entry[self::KEY_VISIBILITY] = $this->guessVisibility($entry[self::KEY_MODE]);
+ } else {
+ $entry[self::KEY_VISIBILITY] = false;
+ }
+ }
+
+ /**
+ * @param $entry
+ */
+ private function setEntryName(&$entry)
+ {
+ if (isset($entry[self::KEY_NAME]) === false) {
+ if (isset($entry[self::KEY_FILENAME]) === true) {
+ $entry[self::KEY_NAME] = $entry[self::KEY_FILENAME];
+ } elseif (isset($entry[self::KEY_PATH]) === true) {
+ $entry[self::KEY_NAME] = $entry[self::KEY_PATH];
+ } else {
+ $entry[self::KEY_NAME] = null;
+ }
+ }
+ }
+}
diff --git a/src/ApiInterface.php b/src/ApiInterface.php
new file mode 100644
index 0000000..d8cb101
--- /dev/null
+++ b/src/ApiInterface.php
@@ -0,0 +1,53 @@
+client = $client;
- $this->settings = $settings;
-
- /* @NOTE: If $settings contains `credentials` but not an `author` we are
- * still in `read-only` mode.
- */
- list($this->vendor, $this->package) = explode('/', $this->settings->getRepository());
- }
-
- /**
- * @param string $path
- *
- * @return bool
- */
- final public function exists($path)
- {
- return $this->repositoryContents()->exists(
- $this->vendor,
- $this->package,
- $path,
- $this->settings->getReference()
- );
- }
-
- /**
- * @param $path
- *
- * @return null|string
- *
- * @throws \Github\Exception\ErrorException
- */
- final public function download($path)
- {
- $fileContent = $this->repositoryContents()->download(
- $this->vendor,
- $this->package,
- $path,
- $this->settings->getReference()
- );
-
- return $fileContent;
- }
-
- /**
- * @param string $path
- * @param bool $recursive
- *
- * @return array
- */
- final public function metadata($path, $recursive)
- {
- // If $info['truncated'] is `true`, the number of items in the tree array
- // exceeded the github maximum limit. If you need to fetch more items,
- // multiple calls will be needed
-
- $info = $this->trees($recursive);
- $tree = $this->getPathFromTree($info, $path, $recursive);
- $result = $this->normalizeMetadata($tree);
-
- return $result;
- }
-
- /**
- * @param string $path
- *
- * @return array
- */
- final public function show($path)
- {
- // Get information about a repository file or directory
- $fileInfo = $this->repositoryContents()->show(
- $this->vendor,
- $this->package,
- $path,
- $this->settings->getReference()
- );
- return $fileInfo;
- }
-
- /**
- * @param string $path
- *
- * @return array|bool
- */
- final public function getMetaData($path)
- {
- try {
- $metadata = $this->show($path);
- } catch (RuntimeException $exception) {
- if ($exception->getMessage() === self::ERROR_NOT_FOUND) {
- $metadata = false;
- } else {
- throw $exception;
- }
- }
-
- return $metadata;
- }
-
- /**
- * @param string $path
- *
- * @return null|string
- */
- final public function guessMimeType($path)
- {
- //@NOTE: The github API does not return a MIME type, so we have to guess :-(
- if (strrpos($path, '.') > 1) {
- $extension = substr($path, strrpos($path, '.')+1);
- }
-
- if (isset($extension)) {
- $mimeType = MimeType::detectByFileExtension($extension) ?: 'text/plain';
- } else {
- $content = $this->download($path);
- $mimeType = MimeType::detectByContent($content);
- }
-
- return $mimeType;
- }
-
- /**
- * @param string $path
- *
- * @return array
- */
- final public function updated($path)
- {
- // List commits for a file
- $commits = $this->repository()->commits()->all(
- $this->vendor,
- $this->package,
- array(
- 'sha' => $this->settings->getBranch(),
- 'path' => $path
- )
- );
-
- $updated = array_shift($commits);
- //@NOTE: $created = array_pop($commits);
-
- $time = new \DateTime($updated['commit']['committer']['date']);
-
- return ['timestamp' => $time->getTimestamp()];
- }
-
- /**
- * @return \Github\Api\Repository\Contents
- */
- private function repositoryContents()
- {
- return $this->repository()->contents();
- }
-
- /**
- *
- */
- private function authenticate()
- {
- static $hasRun;
-
- if ($hasRun === null) {
- if (empty($this->settings->getCredentials()) === false) {
- $credentials = array_replace(
- [null, null, null],
- $this->settings->getCredentials()
- );
-
- $this->client->authenticate(
- $credentials[1],
- $credentials[2],
- $credentials[0]
- );
- }
- $hasRun = true;
- }
- }
-
- /**
- * @return Repo
- */
- private function repository()
- {
- return $this->fetchApi(self::KEY_REPO);
- }
-
- /**
- * @param string $name
- * @return \Github\Api\ApiInterface
- */
- private function fetchApi($name)
- {
- $this->authenticate();
- return $this->client->api($name);
- }
-
- /**
- * @param array $metadata
- * @param string $path
- * @param bool $recursive
- *
- * @return array
- */
- private function getPathFromTree(array $metadata, $path, $recursive)
- {
- if (empty($path)) {
- if ($recursive === false) {
- $metadata = array_filter($metadata, function ($entry) use ($path) {
- return (strpos($entry[self::KEY_PATH], '/', strlen($path)) === false);
- });
- }
- } else {
- $metadata = array_filter($metadata, function ($entry) use ($path, $recursive) {
- $match = false;
-
- if (strpos($entry[self::KEY_PATH], $path) === 0) {
- if ($recursive === true) {
- $match = true;
- } else {
- $length = strlen($path);
- $match = (strpos($entry[self::KEY_PATH], '/', $length) === false);
- }
- }
-
- return $match;
- });
- }
-
- return $metadata;
- }
-
- /**
- * @param array $metadata
- *
- * @return array
- */
- private function normalizeMetadata($metadata)
- {
- $result = [];
-
- if (is_array(current($metadata)) === false) {
- $metadata = [$metadata];
- }
-
- foreach ($metadata as $entry) {
- if (isset($entry[self::KEY_NAME]) === false){
- if(isset($entry[self::KEY_FILENAME]) === true) {
- $entry[self::KEY_NAME] = $entry[self::KEY_FILENAME];
- } elseif(isset($entry[self::KEY_PATH]) === true) {
- $entry[self::KEY_NAME] = $entry[self::KEY_PATH];
- } else {
- // ?
- }
- }
-
- if (isset($entry[self::KEY_TYPE]) === true) {
- switch ($entry[self::KEY_TYPE]) {
- case self::KEY_BLOB:
- $entry[self::KEY_TYPE] = self::KEY_FILE;
- break;
-
- case self::KEY_TREE:
- $entry[self::KEY_TYPE] = self::KEY_DIRECTORY;
- break;
- }
- }
-
- if (isset($entry[self::KEY_CONTENTS]) === false) {
- $entry[self::KEY_CONTENTS] = false;
- }
-
- if (isset($entry[self::KEY_STREAM]) === false) {
- $entry[self::KEY_STREAM] = false;
- }
-
- if (isset($entry[self::KEY_TIMESTAMP]) === false) {
- $entry[self::KEY_TIMESTAMP] = false;
- }
-
- if (isset($entry[self::KEY_MODE])) {
- $entry[self::KEY_VISIBILITY] = $this->visibility($entry[self::KEY_MODE]);
- } else {
- $entry[self::KEY_VISIBILITY] = false;
- }
-
- $result[] = $entry;
- }
-
- return $result;
- }
-
- /**
- * @return GitData
- */
- private function gitData()
- {
- return $this->fetchApi(self::KEY_GIT_DATA);
- }
-
- /**
- * @param bool $recursive
- * @return \Guzzle\Http\EntityBodyInterface|mixed|string
- */
- private function trees($recursive)
- {
- $trees = $this->gitData()->trees();
-
- $info = $trees->show(
- $this->vendor,
- $this->package,
- $this->settings->getReference(),
- $recursive
- );
-
- return $info[self::KEY_TREE];
- }
-
- /**
- * @param $permissions
- * @return string
- */
- private function visibility($permissions)
- {
- return $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
- }
-}
diff --git a/src/GithubAdapter.php b/src/GithubAdapter.php
index c4e5d2a..f48345f 100644
--- a/src/GithubAdapter.php
+++ b/src/GithubAdapter.php
@@ -21,15 +21,23 @@ class GithubAdapter extends AbstractAdapter
const VISIBILITY_PRIVATE = 'private';
const VISIBILITY_PUBLIC = 'public';
- /** @var Client */
- private $client;
+ /** @var ApiInterface */
+ private $api;
/**
- * @param Client $client
+ * @return ApiInterface
*/
- public function __construct(Client $client)
+ final public function getApi()
{
- $this->client = $client;
+ return $this->api;
+ }
+
+ /**
+ * @param ApiInterface $api
+ */
+ public function __construct(ApiInterface $api)
+ {
+ $this->api = $api;
}
/**
@@ -44,7 +52,7 @@ public function __construct(Client $client)
public function write($path, $contents, Config $config)
{
throw new Exception('Write action are not (yet) supported');
- //@TODO: return $this->client->create($path, $contents);
+ //@TODO: return $this->getApi()->create($path, $contents);
}
/**
@@ -59,7 +67,7 @@ public function write($path, $contents, Config $config)
public function update($path, $contents, Config $config)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->update($path, $contents);
+ // @TODO: return $this->getApi()->update($path, $contents);
}
/**
@@ -73,7 +81,7 @@ public function update($path, $contents, Config $config)
public function rename($path, $newpath)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->rename($path, $newPath);
+ // @TODO: return $this->getApi()->rename($path, $newPath);
}
/**
@@ -87,7 +95,7 @@ public function rename($path, $newpath)
public function copy($path, $newpath)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->copy($path, $newPath);
+ // @TODO: return $this->getApi()->copy($path, $newPath);
}
/**
@@ -100,7 +108,7 @@ public function copy($path, $newpath)
public function delete($path)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->delete($path);
+ // @TODO: return $this->getApi()->delete($path);
}
/**
@@ -113,7 +121,7 @@ public function delete($path)
public function deleteDir($dirname)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->deleteDir($dirname);
+ // @TODO: return $this->getApi()->deleteDir($dirname);
}
/**
@@ -127,7 +135,7 @@ public function deleteDir($dirname)
public function createDir($dirname, Config $config)
{
throw new Exception('Write action are not (yet) supported');
- // @TODO: return $this->client->createDir($dirname);
+ // @TODO: return $this->getApi()->createDir($dirname);
}
/**
@@ -152,7 +160,7 @@ public function setVisibility($path, $visibility)
*/
public function has($path)
{
- return $this->client->exists($path);
+ return $this->getApi()->exists($path);
}
/**
@@ -164,7 +172,7 @@ public function has($path)
*/
public function read($path)
{
- return [Client::KEY_CONTENTS => $this->client->download($path)];
+ return [ApiInterface::KEY_CONTENTS => $this->getApi()->getFileContents($path)];
}
/**
@@ -177,7 +185,7 @@ public function read($path)
*/
public function listContents($path = '/', $recursive = false)
{
- return $this->client->metadata($path, $recursive);
+ return $this->getApi()->getRecursiveMetadata($path, $recursive);
}
/**
@@ -189,7 +197,7 @@ public function listContents($path = '/', $recursive = false)
*/
public function getMetadata($path)
{
- return $this->client->show($path);
+ return $this->getApi()->getMetaData($path);
}
/**
@@ -201,7 +209,7 @@ public function getMetadata($path)
*/
public function getSize($path)
{
- return $this->client->getMetaData($path);
+ return $this->getApi()->getMetaData($path);
}
/**
@@ -213,7 +221,7 @@ public function getSize($path)
*/
public function getMimetype($path)
{
- return ['mimetype' => $this->client->guessMimeType($path)];
+ return ['mimetype' => $this->getApi()->guessMimeType($path)];
}
/**
@@ -225,7 +233,7 @@ public function getMimetype($path)
*/
public function getTimestamp($path)
{
- return $this->client->updated($path);
+ return $this->getApi()->getLastUpdatedTimestamp($path);
}
/**
@@ -238,7 +246,7 @@ public function getTimestamp($path)
public function getVisibility($path)
{
$recursive = false;
- $metadata = $this->client->metadata($path, $recursive);
+ $metadata = $this->getApi()->getRecursiveMetadata($path, $recursive);
return $metadata[0];
}
}
diff --git a/src/Settings.php b/src/Settings.php
index 706eb28..8ea14b1 100644
--- a/src/Settings.php
+++ b/src/Settings.php
@@ -4,42 +4,53 @@
use Github\Client;
-class Settings
+class Settings implements SettingsInterface
{
+ ////////////////////////////// CLASS PROPERTIES \\\\\\\\\\\\\\\\\\\\\\\\\\\\
const AUTHENTICATE_USING_TOKEN = Client::AUTH_URL_TOKEN;
const AUTHENTICATE_USING_PASSWORD = Client::AUTH_HTTP_PASSWORD;
- const REFERENCE_HEAD = 'HEAD';
const BRANCH_MASTER = 'master';
+ const REFERENCE_HEAD = 'HEAD';
+
+ const ERROR_INVALID_REPOSITORY_NAME = 'Given Repository name "%s" should be in the format of "vendor/project"';
/** @var string */
- private $repository;
- /** @var string */
- private $reference = self::REFERENCE_HEAD;
+ private $branch;
/** @var array */
private $credentials;
/** @var string */
- private $branch = self::BRANCH_MASTER;
+ private $reference;
+ /** @var string */
+ private $repository;
+ /** @var string */
+ private $vendor;
+ /** @var string */
+ private $package;
- final public function __construct(
- $repository,
- array $credentials = [],
- $branch = self::BRANCH_MASTER,
- $reference = self::REFERENCE_HEAD
- ) {
- $this->branch = $branch;
- $this->credentials = $credentials;
- $this->reference = $reference;
- $this->repository = $repository;
+ //////////////////////////// SETTERS AND GETTERS \\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @return string
+ */
+ final public function getBranch()
+ {
+ return $this->branch;
}
+ /**
+ * @return array
+ */
+ final public function getCredentials()
+ {
+ return $this->credentials;
+ }
/**
* @return string
*/
- final public function getRepository()
+ final public function getPackage()
{
- return $this->repository;
+ return $this->package;
}
/**
@@ -51,19 +62,55 @@ final public function getReference()
}
/**
- * @return array
+ * @return string
*/
- final public function getCredentials()
+ final public function getRepository()
{
- return $this->credentials;
+ return $this->repository;
}
/**
* @return string
*/
- final public function getBranch()
+ final public function getVendor()
{
- return $this->branch;
+ return $this->vendor;
+ }
+
+ //////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ final public function __construct(
+ $repository,
+ array $credentials = [],
+ $branch = self::BRANCH_MASTER,
+ $reference = self::REFERENCE_HEAD
+ ) {
+ $this->isValidRepositoryName($repository);
+
+ $this->branch = (string) $branch;
+ $this->credentials = $credentials;
+ $this->reference = (string) $reference;
+ $this->repository = (string) $repository;
+
+ list($this->vendor, $this->package) = explode('/', $repository);
+ }
+
+ ////////////////////////////// UTILITY METHODS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @param $repository
+ */
+ private function isValidRepositoryName($repository)
+ {
+ if (is_string($repository) === false
+ || strpos($repository, '/') === false
+ || strpos($repository, '/') === 0
+ || substr_count($repository, '/') !== 1
+ ) {
+ $message = sprintf(
+ self::ERROR_INVALID_REPOSITORY_NAME,
+ var_export($repository, true)
+ );
+ throw new \InvalidArgumentException($message);
+ }
}
}
diff --git a/src/SettingsInterface.php b/src/SettingsInterface.php
new file mode 100644
index 0000000..92518bf
--- /dev/null
+++ b/src/SettingsInterface.php
@@ -0,0 +1,36 @@
+
+ * @covers ::__construct
+ */
+class ApiTest extends \PHPUnit_Framework_TestCase
+{
+ ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ const MOCK_FILE_PATH = '/path/to/mock/file';
+ const MOCK_FILE_CONTENTS = 'Mock file contents';
+
+ /** @var Api */
+ private $api;
+ /** @var Client|\PHPUnit_Framework_MockObject_MockObject */
+ private $mockClient;
+ /** @var Settings|\PHPUnit_Framework_MockObject_MockObject */
+ private $mockSettings;
+
+ /**
+ *
+ */
+ protected function setUp()
+ {
+ $this->mockClient = $this->getMockClient();
+ $this->mockSettings = $this->getMockSettings();
+
+ $this->api = new Api($this->mockClient, $this->mockSettings);
+ }
+
+ /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @uses Potherca\Flysystem\Github\Api::exists
+ */
+ final public function testApiShouldComplainWhenInstantiatedWithoutClient()
+ {
+ $message = sprintf(
+ 'Argument %d passed to %s::__construct() must be an instance of %s',
+ 1,
+ Api::class,
+ Client::class
+ );
+
+ $this->setExpectedException(
+ \PHPUnit_Framework_Error::class,
+ $message
+ );
+
+ /** @noinspection PhpParamsInspection */
+ new Api();
+ }
+
+ /**
+ * @coversNothing
+ */
+ final public function testApiShouldComplainWhenInstantiatedWithoutSettings()
+ {
+ $message = sprintf(
+ 'Argument %d passed to %s::__construct() must implement interface %s',
+ 2,
+ Api::class,
+ SettingsInterface::class
+ );
+
+ $this->setExpectedException(
+ \PHPUnit_Framework_Error::class,
+ $message
+ );
+
+ /** @noinspection PhpParamsInspection */
+ new Api($this->getMockClient());
+ }
+
+ /**
+ * @covers ::getFileContents
+ */
+ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForFileContent()
+ {
+ $api = $this->api;
+
+ $expected = self::MOCK_FILE_CONTENTS;
+
+ $mockVendor = 'vendor';
+ $mockPackage = 'package';
+ $mockReference = 'reference';
+
+ $this->prepareMockSettings([
+ 'getVendor' => $mockVendor,
+ 'getPackage' => $mockPackage,
+ 'getReference' => $mockReference,
+ ]);
+
+ $this->prepareMockApi(
+ 'download',
+ $api::API_REPO,
+ [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference],
+ $expected
+ );
+
+ $actual = $api->getFileContents(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::exists
+ */
+ final public function testApiShouldUseValuesFromSettingsWhenAskingClientIfFileExists()
+ {
+ $api = $this->api;
+
+ $expected = self::MOCK_FILE_CONTENTS;
+
+ $mockVendor = 'vendor';
+ $mockPackage = 'package';
+ $mockReference = 'reference';
+
+ $this->prepareMockSettings([
+ 'getVendor' => $mockVendor,
+ 'getPackage' => $mockPackage,
+ 'getReference' => $mockReference,
+ ]);
+
+ $this->prepareMockApi(
+ 'exists',
+ $api::API_REPO,
+ [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference],
+ $expected
+ );
+
+ $actual = $api->exists(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getLastUpdatedTimestamp
+ */
+ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForLastUpdatedTimestamp()
+ {
+ $api = $this->api;
+
+ $expected = ['timestamp' => 1420070400];
+
+ $this->prepareFixturesForTimeStamp();
+
+ $actual = $api->getLastUpdatedTimestamp(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getCreatedTimestamp
+ */
+ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForCreatedTimestamp()
+ {
+ $api = $this->api;
+
+ $expected = ['timestamp' => 1362268800];
+
+ $this->prepareFixturesForTimeStamp();
+
+ $actual = $api->getCreatedTimestamp(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+ /**
+ * @covers ::getMetaData
+ */
+ final public function testApiShouldUseValuesFromSettingsWhenAskingClientForFileInfo()
+ {
+ $api = $this->api;
+
+ $expected = self::MOCK_FILE_CONTENTS;
+
+ $mockVendor = 'vendor';
+ $mockPackage = 'package';
+ $mockReference = 'reference';
+
+ $this->prepareMockSettings([
+ 'getVendor' => $mockVendor,
+ 'getPackage' => $mockPackage,
+ 'getReference' => $mockReference,
+ ]);
+
+ $this->prepareMockApi(
+ 'show',
+ $api::API_REPO,
+ [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference],
+ $expected
+ );
+
+ $actual = $api->getMetaData(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getMetaData
+ */
+ final public function testApiShouldAccountForFileNotExistingWhenAskingInfoForFile()
+ {
+ $api = $this->api;
+
+ $expected = false;
+
+ $this->mockClient->expects($this->exactly(1))
+ ->method('api')
+ ->willThrowException(new RuntimeException(Api::ERROR_NOT_FOUND));
+
+ $actual = $api->getMetaData(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getMetaData
+ */
+ final public function testApiShouldPassOtherRuntimeExceptionsWhenAskingInfoForFileCausesRuntimeException()
+ {
+ $api = $this->api;
+
+ $this->setExpectedException(RuntimeException::class, self::MOCK_FILE_CONTENTS);
+
+ $expected = false;
+
+ $this->mockClient->expects($this->exactly(1))
+ ->method('api')
+ ->willThrowException(new RuntimeException(self::MOCK_FILE_CONTENTS));
+
+ $actual = $api->getMetaData(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getMetaData
+ */
+ final public function testApiShouldPassOnExceptionsWhenAskingInfoForFileCausesAnException()
+ {
+ $api = $this->api;
+
+ $this->setExpectedException(\RuntimeException::class, Api::ERROR_NOT_FOUND);
+
+ $expected = false;
+
+ $this->mockClient->expects($this->exactly(1))
+ ->method('api')
+ ->willThrowException(new \RuntimeException(Api::ERROR_NOT_FOUND));
+
+ $actual = $api->getMetaData(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getRecursiveMetadata
+ *
+ * @dataProvider provideExpectedMetadata
+ *
+ * @param string $path
+ * @param array $expected
+ * @param bool $recursive
+ * @param bool $truncated
+ */
+ final public function testApiShouldRetrieveExpectedMetadataWhenAskedTogetRecursiveMetadata(
+ $path,
+ $expected,
+ $recursive,
+ $truncated
+ ) {
+ $api = $this->api;
+
+ $mockVendor = 'vendor';
+ $mockPackage = 'package';
+ $mockReference = 'reference';
+
+ $this->prepareMockSettings([
+ 'getVendor' => $mockVendor,
+ 'getPackage' => $mockPackage,
+ 'getReference' => $mockReference,
+ ]);
+
+ $this->prepareMockApi(
+ 'show',
+ $api::API_GIT_DATA,
+ [$mockVendor, $mockPackage, $mockReference, $recursive],
+ $this->getMockApiTreeResponse($truncated, $api),
+ Trees::class
+ );
+
+ $actual = $api->getRecursiveMetadata($path, $recursive);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::guessMimeType
+ *
+ * @uses League\Flysystem\Util\MimeType
+ */
+ final public function testApiShouldUseFileExtensionToGuessMimeTypeWhenExtensionIsAvailable()
+ {
+ $api = $this->api;
+
+ $expected = 'image/png';
+
+ $this->mockClient->expects($this->never())->method('api');
+
+ $actual = $api->guessMimeType(self::MOCK_FILE_PATH.'.png');
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::guessMimeType
+ *
+ * @uses League\Flysystem\Util\MimeType
+ *
+ * @uses Potherca\Flysystem\Github\Api::getFileContents
+ */
+ final public function testApiShouldUseFileContentsToGuessMimeTypeWhenExtensionUnavailable()
+ {
+ $api = $this->api;
+
+ $expected = 'image/png';
+
+ $mockVendor = 'vendor';
+ $mockPackage = 'package';
+ $mockReference = 'reference';
+
+ $this->prepareMockSettings([
+ 'getVendor' => $mockVendor,
+ 'getPackage' => $mockPackage,
+ 'getReference' => $mockReference,
+ ]);
+
+ $image = imagecreatetruecolor(1,1);
+ ob_start();
+ imagepng($image);
+ $contents = ob_get_contents();
+ ob_end_clean();
+ imagedestroy($image);
+
+ $this->prepareMockApi(
+ 'download',
+ $api::API_REPO,
+ [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference],
+ $contents
+ );
+
+ $actual = $api->guessMimeType(self::MOCK_FILE_PATH);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @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,
+ 'getCredentials' => ['foo']
+ ]);
+
+ $this->prepareMockApi(
+ 'exists',
+ $api::API_REPO,
+ [$mockVendor, $mockPackage, self::MOCK_FILE_PATH, $mockReference],
+ ''
+ );
+
+ $this->mockClient->expects($this->exactly(1))
+ ->method('authenticate')
+ ;
+
+ $api->exists(self::MOCK_FILE_PATH);
+ }
+
+ ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @return Client|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockClient()
+ {
+ return $this->getMockBuilder(Client::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ /**
+ * @return Settings|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMockSettings()
+ {
+ return $this->getMockBuilder(SettingsInterface::class)
+ ->getMock();
+ }
+
+ /**
+ * @param string $method
+ * @param string $apiName
+ * @param array $apiParameters
+ * @param mixed $apiOutput
+ * @param string $repositoryClass
+ */
+ private function prepareMockApi($method, $apiName, $apiParameters, $apiOutput, $repositoryClass = Contents::class)
+ {
+
+ $parts = explode('\\', $repositoryClass);
+ $repositoryName = strtolower(array_pop($parts));
+
+ $mockApi = $this->getMockBuilder(ApiInterface::class)
+ ->setMethods([$repositoryName, 'getPerPage', 'setPerPage'])
+ ->getMock()
+ ;
+
+ $mockRepository = $this->getMockBuilder($repositoryClass)
+ ->disableOriginalConstructor()
+ ->getMock()
+ ;
+
+ $mockRepository->expects($this->exactly(1))
+ ->method($method)
+ ->withAnyParameters()
+ ->willReturnCallback(function () use ($apiParameters, $apiOutput) {
+ $this->assertEquals($apiParameters, func_get_args());
+ return $apiOutput;
+ })
+ ;
+
+ $mockApi->expects($this->exactly(1))
+ ->method($repositoryName)
+ ->willReturn($mockRepository)
+ ;
+
+ $this->mockClient->expects($this->exactly(1))
+ ->method('api')
+ ->with($apiName)
+ ->willReturn($mockApi)
+ ;
+ }
+
+ /**
+ * @param array $expectations
+ */
+ private function prepareMockSettings(array $expectations)
+ {
+ foreach ($expectations as $methodName => $returnValue) {
+ $this->mockSettings->expects($this->exactly(1))
+ ->method($methodName)
+ ->willReturn($returnValue)
+ ;
+ }
+ }
+
+ /**
+ * @param $truncated
+ * @param $api
+ * @return array
+ */
+ private function getMockApiTreeResponse($truncated, $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,
+ ]);
+
+ $apiParameters = [
+ $mockVendor,
+ $mockPackage,
+ [
+ 'sha' => $mockBranch,
+ 'path' => self::MOCK_FILE_PATH
+ ]
+
+ ];
+
+ $apiOutput = [
+ ['commit' => ['committer' => ['date' => '20150101']]],
+ ['commit' => ['committer' => ['date' => '20140202']]],
+ ['commit' => ['committer' => ['date' => '20130303']]],
+ ];
+
+ $this->prepareMockApi(
+ 'all',
+ Api::API_REPO,
+ $apiParameters,
+ $apiOutput,
+ Commits::class
+ );
+ }
+
+ /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @return array
+ */
+ final public function provideExpectedMetadata()
+ {
+ return [
+ 'Filepath, not recursive, not truncated' => [
+ self::MOCK_FILE_PATH,
+ [
+ [
+ 'path' => '/path/to/mock/file',
+ 'mode' => 100644,
+ 'type' => 'dir',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file',
+ 'contents' => null,
+ 'stream' => null,
+ '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' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ]
+ ],
+ false,
+ false
+ ],
+ 'Filepath, recursive, not truncated' => [
+ self::MOCK_FILE_PATH,
+ [
+ [
+ 'path' => '/path/to/mock/file',
+ 'mode' => 100644,
+ 'type' => 'dir',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file',
+ 'contents' => null,
+ 'stream' => null,
+ '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' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ],
+ [
+ 'path' => '/path/to/mock/file/Bar',
+ 'mode' => 100644,
+ 'type' => 'file',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file/Bar',
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ]
+ ],
+ true,
+ false
+ ],
+ 'Filepath, not recursive, truncated' => [
+ self::MOCK_FILE_PATH,
+ [
+ [
+ 'path' => '/path/to/mock/file',
+ 'mode' => 100644,
+ 'type' => 'dir',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file',
+ 'contents' => null,
+ 'stream' => null,
+ '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' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ]
+ ],
+ false,
+ true
+ ],
+ 'No Filepath, recursive, not truncated' => [
+ '',
+ [
+ [
+ 'path' => '/path/to/mock/file',
+ 'mode' => 100644,
+ 'type' => 'dir',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file',
+ 'contents' => null,
+ 'stream' => null,
+ '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' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ],
+ [
+ 'path' => '/path/to/mock/file/Bar',
+ 'mode' => 100644,
+ 'type' => 'file',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file/Bar',
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ],
+ [
+ 'path' => 'some/other/file',
+ 'mode' => 100644,
+ 'type' => 'file',
+ 'size' => 747,
+ 'name' => 'some/other/file',
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ]
+ ],
+ true,
+ false
+ ],
+ 'No Filepath, recursive, truncated' => [
+ '',
+ [
+ [
+ 'path' => '/path/to/mock/file',
+ 'mode' => 100644,
+ 'type' => 'dir',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file',
+ 'contents' => null,
+ 'stream' => null,
+ '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' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ],
+ [
+ 'path' => '/path/to/mock/file/Bar',
+ 'mode' => 100644,
+ 'type' => 'file',
+ 'size' => 57,
+ 'name' => '/path/to/mock/file/Bar',
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ],
+ [
+ 'path' => 'some/other/file',
+ 'mode' => 100644,
+ 'type' => 'file',
+ 'size' => 747,
+ 'name' => 'some/other/file',
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ 'visibility' => 'public'
+ ]
+ ],
+ true,
+ true
+ ],
+ 'No Filepath, not recursive, truncated' => [
+ '',
+ [
+ [
+ 'name' => null,
+ 'visibility' => null,
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null
+ ]
+ ],
+ false,
+ true
+ ],
+ 'No Filepath, not recursive, not truncated' => [
+ '',
+ [
+ [
+ 'name' => null,
+ 'visibility' => null,
+ 'contents' => null,
+ 'stream' => null,
+ 'timestamp' => null,
+ ]
+ ],
+ false,
+ false
+ ],
+ ];
+ }
+}
diff --git a/tests/GithubAdapterTest.php b/tests/GithubAdapterTest.php
new file mode 100644
index 0000000..54c10af
--- /dev/null
+++ b/tests/GithubAdapterTest.php
@@ -0,0 +1,76 @@
+
+ * @covers ::__construct
+ * @covers ::getApi
+ */
+class GithubAdapterTest extends \PHPUnit_Framework_TestCase
+{
+ ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ const MOCK_FILE_PATH = '/path/to/mock/file';
+
+ /** @var GithubAdapter */
+ private $adapter;
+ /** @var ApiInterface|\PHPUnit_Framework_MockObject_MockObject */
+ private $mockClient;
+
+ /**
+ *
+ */
+ protected function setup()
+ {
+ $this->mockClient = $this->getMock(ApiInterface::class);
+ $this->adapter = new GithubAdapter($this->mockClient);
+ }
+
+ /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @covers ::has
+ * @covers ::read
+ * @covers ::listContents
+ * @covers ::getMetadata
+ * @covers ::getSize
+ * @covers ::getMimetype
+ * @covers ::getTimestamp
+ * @covers ::getVisibility
+ *
+ * @dataProvider provideReadMethods
+ *
+ * @param $method
+ * @param $apiMethod
+ * @param $parameters
+ */
+ final public function testAdapterShouldPassParameterToClient($method, $apiMethod, $parameters)
+ {
+ $mocker = $this->mockClient->expects($this->exactly(1))
+ ->method($apiMethod);
+
+ $mocker->getMatcher()->parametersMatcher = new \PHPUnit_Framework_MockObject_Matcher_Parameters($parameters);
+
+ call_user_func_array([$this->adapter, $method], $parameters);
+ }
+
+ ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+
+ /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ final public function provideReadMethods()
+ {
+ return [
+ ['has', 'exists', [self::MOCK_FILE_PATH]],
+ ['read', 'getFileContents', [self::MOCK_FILE_PATH]],
+ ['listContents', 'getRecursiveMetadata', [self::MOCK_FILE_PATH, true]],
+ ['getMetadata', 'getMetadata', [self::MOCK_FILE_PATH]],
+ ['getSize', 'getMetadata', [self::MOCK_FILE_PATH]],
+ ['getMimetype', 'guessMimeType', [self::MOCK_FILE_PATH]],
+ ['getTimestamp', 'getLastUpdatedTimestamp', [self::MOCK_FILE_PATH]],
+ ['getVisibility', 'getRecursiveMetadata', [self::MOCK_FILE_PATH]],
+ ];
+ }
+}
diff --git a/tests/SettingsTest.php b/tests/SettingsTest.php
new file mode 100644
index 0000000..bd46a24
--- /dev/null
+++ b/tests/SettingsTest.php
@@ -0,0 +1,239 @@
+
+ * @covers ::__construct
+ */
+class SettingsTest extends \PHPUnit_Framework_TestCase
+{
+ ////////////////////////////////// FIXTURES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ const MOCK_VENDOR_NAME = 'mock_vendor';
+ const MOCK_PACKAGE_NAME = 'mock_package';
+ const MOCK_BRANCH = 'mock_branch';
+ const MOCK_REFERENCE = 'mock_reference';
+
+ /** @var Settings */
+ private $settings;
+
+ /**
+ *
+ */
+ final protected function setUp()
+ {
+ $this->settings = new Settings(
+ $this->getMockRespositoryName(),
+ $this->getMockCredentials(),
+ self::MOCK_BRANCH,
+ self::MOCK_REFERENCE
+ );
+ }
+
+ /////////////////////////////////// TESTS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @covers ::__construct
+ */
+ final public function testSettingsShouldComplainWhenInstantiatedWithoutRepositoryName()
+ {
+ $this->setExpectedException(
+ \PHPUnit_Framework_Error_Warning::class,
+ sprintf('Missing argument %d for %s::__construct()', 1, Settings::class)
+ );
+
+ /** @noinspection PhpParamsInspection */
+ new Settings();
+ }
+
+ /**
+ * @covers ::getRepository
+ */
+ final public function testSettingsShouldContainRepositoryItWasGivenGivenWhenInstantiated()
+ {
+ $settings = $this->settings;
+
+ $expected = $this->getMockRespositoryName();
+
+ $actual = $settings->getRepository();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::__construct
+ *
+ * @dataProvider provideInvalidRepositoryNames
+ *
+ * @param string $name
+ */
+ final public function testSettingsShouldComplainWhenGivenInvalidRepositoryNames($name)
+ {
+ $this->setExpectedException(
+ \InvalidArgumentException::class,
+ sprintf(Settings::ERROR_INVALID_REPOSITORY_NAME, var_export($name, true))
+ );
+ new Settings($name);
+ }
+
+ /**
+ * @covers ::getRepository
+ */
+ final public function testSettingsShouldOnlyNeedRepositoryNameWhenInstantiated()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+ $this->assertInstanceOf(Settings::class, $settings);
+ }
+
+ /**
+ * @covers ::getVendor
+ */
+ final public function testSettingsShouldContainVendorNameFromGivenRepositoryWhenInstantiated()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+
+ $expected = self::MOCK_VENDOR_NAME;
+
+ $actual = $settings->getVendor();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getPackage
+ */
+ final public function testSettingsShouldContainPackageNameFromGivenRepositoryWhenInstantiated()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+
+ $expected = self::MOCK_PACKAGE_NAME;
+
+ $actual = $settings->getPackage();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getCredentials
+ */
+ final public function testSettingsShouldContainEmptyCredentialsWhenInstantiatedWithoutCredentials()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+
+ $expected = [];
+
+ $actual = $settings->getCredentials();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getCredentials
+ */
+ final public function testSettingsShouldContainCredentialsItWasGivenGivenWhenInstantiated()
+ {
+ $settings = $this->settings;
+
+ $expected = $this->getMockCredentials();
+
+ $actual = $settings->getCredentials();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getBranch
+ */
+ final public function testSettingsShouldContainMasterAsBranchWhenInstantiatedWithoutBranch()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+
+ $expected = 'master';
+
+ $actual = $settings->getBranch();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getBranch
+ */
+ final public function testSettingsShouldContainBranchItWasGivenGivenWhenInstantiated()
+ {
+ $settings = $this->settings;
+
+ $expected = self::MOCK_BRANCH;
+
+ $actual = $settings->getBranch();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getReference
+ */
+ final public function testSettingsShouldContainHeadAsReferenceWhenInstantiatedWithoutReference()
+ {
+ $settings = new Settings($this->getMockRespositoryName());
+
+ $expected = 'HEAD';
+
+ $actual = $settings->getReference();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @covers ::getReference
+ */
+ final public function testSettingsShouldContaingetReferenceItWasGivenGivenWhenInstantiated()
+ {
+ $settings = $this->settings;
+
+ $expected = self::MOCK_REFERENCE;
+
+ $actual = $settings->getReference();
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ ////////////////////////////// MOCKS AND STUBS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ /**
+ * @return string
+ */
+ private function getMockRespositoryName()
+ {
+ return self::MOCK_VENDOR_NAME . '/' . self::MOCK_PACKAGE_NAME;
+ }
+
+ /**
+ * @return array
+ */
+ private function getMockCredentials()
+ {
+ return ['mock_type', 'mock_user', 'mock_password'];
+ }
+
+ /////////////////////////////// DATAPROVIDERS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
+ final public function provideInvalidRepositoryNames()
+ {
+ return [
+ [''],
+ [null],
+ [true],
+ [array()],
+ ['foo'],
+ ['/foo'],
+ ['foo/bar/'],
+ ['/foo/bar/'],
+ ['foo/bar/baz'],
+ ['/foo/bar/baz/'],
+ ['foo/bar/baz/'],
+ ['/foo/bar/baz'],
+ ];
+ }
+}
+
+/*EOF*/