diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf5206f..1feb999 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - php-version: [7.4,8.1,8.2] + php-version: [8.1,8.2] runs-on: ubuntu-latest diff --git a/Makefile b/Makefile index 8ecd063..5c2e664 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PHP_CS_RULES=@Symfony,-ordered_imports,-phpdoc_summary,-global_namespace_import,-no_superfluous_phpdoc_tags +PHP_CS_RULES=@Symfony,-global_namespace_import PHP_MD_RULES=cleancode,codesize,controversial,design,naming,unusedcode .PHONY: test @@ -10,10 +10,14 @@ test: check-style check-rules --coverage-html output/coverage .PHONY: check-rules -check-rules: vendor +check-rules: phpstan phpmd + +phpmd: @echo "-- Checking coding rules using phpmd (see @SuppressWarning to bypass control)" vendor/bin/phpmd src text $(PHP_MD_RULES) +phpstan: + vendor/bin/phpstan analyse -c phpstan.neon --error-format=raw .PHONY: fix-style fix-style: vendor @@ -25,8 +29,8 @@ fix-style: vendor .PHONY: check-style check-style: vendor @echo "-- Checking coding style using php-cs-fixer (run 'make fix-style' if it fails)" - vendor/bin/php-cs-fixer fix src --rules $(PHP_CS_RULES) -v --dry-run --diff --using-cache=no - vendor/bin/php-cs-fixer fix tests --rules $(PHP_CS_RULES) -v --dry-run --diff --using-cache=no + vendor/bin/php-cs-fixer fix src --rules $(PHP_CS_RULES) -v --dry-run --diff + vendor/bin/php-cs-fixer fix tests --rules $(PHP_CS_RULES) -v --dry-run --diff vendor: composer install diff --git a/README.md b/README.md index 1ce208d..3435cc0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This module is also used by [mborne/git-manager](https://github.com/mborne/git-m ## Requirements -* PHP >= 7.4 or >= 8.x +* [PHP >= 8.1](https://www.php.net/supported-versions.php) ## Supported GIT hosting services diff --git a/composer.json b/composer.json index 8e79247..e404e12 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ } }, "require": { + "php": ">=8.1", "guzzlehttp/guzzle": "~7.0" }, "require-dev": { @@ -25,6 +26,7 @@ "friendsofphp/php-cs-fixer": "3.17.*", "phpmd/phpmd": "^2.8", "pdepend/pdepend": "2.15.*", - "php-coveralls/php-coveralls": "^2.5" + "php-coveralls/php-coveralls": "^2.5", + "phpstan/phpstan": "^1.10" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..7f33c04 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + paths: + - src + - tests diff --git a/src/AbstractClient.php b/src/AbstractClient.php index eb0c571..d1292ef 100644 --- a/src/AbstractClient.php +++ b/src/AbstractClient.php @@ -2,13 +2,13 @@ namespace MBO\RemoteGit; -use Psr\Log\LoggerInterface; use GuzzleHttp\Client as GuzzleHttpClient; use MBO\RemoteGit\Helper\LoggerHelper; +use Psr\Log\LoggerInterface; /** * Abstract class providing a framework to implement clients - * based on API + * based on API. */ abstract class AbstractClient implements ClientInterface { @@ -23,7 +23,7 @@ abstract class AbstractClient implements ClientInterface protected $logger; /** - * Constructor with an httpClient ready to performs API requests + * Constructor with an httpClient ready to performs API requests. * * @param LoggerInterface $logger * @@ -37,38 +37,34 @@ protected function __construct( $this->logger = LoggerHelper::handleNull($logger); } - /** - * @return GuzzleHttpClient - */ - protected function getHttpClient() + protected function getHttpClient(): GuzzleHttpClient { return $this->httpClient; } - /** - * @return LoggerInterface - */ - protected function getLogger() + protected function getLogger(): LoggerInterface { return $this->logger; } /** - * Create a project according to JSON metadata provided by an API + * Create a project according to JSON metadata provided by an API. * - * @return ProjectInterface + * @param array $rawProject */ - abstract protected function createProject(array $rawProject); + abstract protected function createProject(array $rawProject): ProjectInterface; /** - * Get projets for a given path with parameters + * Get projets for a given path with parameters. + * + * @param array $params * * @return ProjectInterface[] */ protected function getProjects( - $path, + string $path, array $params = [] - ) { + ): array { $uri = $path.'?'.$this->implodeParams($params); $this->getLogger()->debug('GET '.$uri); $response = $this->getHttpClient()->request('GET', $uri); @@ -82,30 +78,28 @@ protected function getProjects( } /** - * Implode params to performs request - * - * @param array $params key=>value + * Implode params to performs HTTP request. * - * @return string + * @param array $params key=>value */ - protected function implodeParams($params) + protected function implodeParams(array $params): string { $parts = []; foreach ($params as $key => $value) { - $parts[] = $key.'='.urlencode($value); + $parts[] = $key.'='.urlencode((string) $value); } return implode('&', $parts); } /** - * Helper to apply filter to a project list + * Helper to apply filter to a project list. * * @param ProjectInterface[] $projects * * @return ProjectInterface[] */ - protected function filter(array $projects, ProjectFilterInterface $filter) + protected function filter(array $projects, ProjectFilterInterface $filter): array { $result = []; foreach ($projects as $project) { diff --git a/src/ClientFactory.php b/src/ClientFactory.php index b67a272..3c7de17 100644 --- a/src/ClientFactory.php +++ b/src/ClientFactory.php @@ -2,16 +2,16 @@ namespace MBO\RemoteGit; -use Psr\Log\LoggerInterface; use GuzzleHttp\Client as GuzzleHttpClient; use MBO\RemoteGit\Exception\ClientNotFoundException; -use MBO\RemoteGit\Helper\LoggerHelper; -use MBO\RemoteGit\Http\TokenType; use MBO\RemoteGit\Github\GithubClient; use MBO\RemoteGit\Gitlab\GitlabClient; use MBO\RemoteGit\Gogs\GogsClient; use MBO\RemoteGit\Helper\ClientHelper; +use MBO\RemoteGit\Helper\LoggerHelper; +use MBO\RemoteGit\Http\TokenType; use MBO\RemoteGit\Local\LocalClient; +use Psr\Log\LoggerInterface; /** * Helper to create clients according to URL. @@ -28,9 +28,9 @@ class ClientFactory private static $instance; /** - * Associates client type to metadata ('className','tokenType') + * Associates client type to metadata ('className','tokenType'). * - * @var array + * @var array> */ private $types = []; @@ -43,7 +43,7 @@ private function __construct() } /** - * True if type is registred + * True if type is registred. * * @param string $type * @@ -55,7 +55,7 @@ public function hasType($type) } /** - * Get supported types + * Get supported types. * * @return string[] */ @@ -65,32 +65,24 @@ public function getTypes() } /** - * Create a client with options - * - * @param LoggerInterface $logger - * - * @return ClientInterface + * Create a client with options. */ public static function createClient( ClientOptions $options, LoggerInterface $logger = null - ) { + ): ClientInterface { return self::getInstance()->createGitClient($options, $logger); } /** - * Create a client with options - * - * @param LoggerInterface $logger - * - * @return ClientInterface + * Create a client with options. * * @SuppressWarnings(PHPMD.StaticAccess) */ public function createGitClient( ClientOptions $options, LoggerInterface $logger = null - ) { + ): ClientInterface { $logger = LoggerHelper::handleNull($logger); /* Detect client type from URL if not specified */ @@ -141,17 +133,16 @@ public function createGitClient( /* create http client */ $httpClient = new GuzzleHttpClient($guzzleOptions); /* create git client */ - return new $clientClass($httpClient, $logger); + $result = new $clientClass($httpClient, $logger); + assert($result instanceof ClientInterface); + + return $result; } /** - * Get client class according to URL content - * - * @param string $url - * - * @return string + * Get client class according to URL content. */ - public static function detectClientClass($url) + public static function detectClientClass(string $url): string { $scheme = parse_url($url, PHP_URL_SCHEME); if (!in_array($scheme, ['http', 'https'])) { @@ -159,9 +150,10 @@ public static function detectClientClass($url) } $hostname = parse_url($url, PHP_URL_HOST); + assert('string' === gettype($hostname)); if ('api.github.com' === $hostname || 'github.com' === $hostname) { return GithubClient::class; - } elseif (false !== strpos($hostname, 'gogs')) { + } elseif (str_contains($hostname, 'gogs')) { return GogsClient::class; } /* @@ -176,7 +168,7 @@ public static function detectClientClass($url) */ public static function getInstance() { - if (is_null(self::$instance)) { + if (null == self::$instance) { self::$instance = new ClientFactory(); } @@ -184,13 +176,13 @@ public static function getInstance() } /** - * Register client type + * Register client type. * - * @param string $className + * @param class-string $className * * @SuppressWarnings(PHPMD.StaticAccess) */ - private function register($className) + private function register(string $className): void { $clientProperties = ClientHelper::getStaticProperties($className); $this->types[$clientProperties['typeName']] = $clientProperties; diff --git a/src/ClientInterface.php b/src/ClientInterface.php index be7e9ef..b3c30ae 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -4,31 +4,29 @@ /** * Lightweight client interface to list hosted git project - * and access files such as composer.json + * and access files such as composer.json. * * @author mborne */ interface ClientInterface { /** - * Find projects throw API + * Find projects throw API. * * @return ProjectInterface[] */ - public function find(FindOptions $options); + public function find(FindOptions $options): array; /** - * Get raw file + * Get raw file. * - * @param string $projectId ex : 123456 - * @param string $filePath ex : composer.json - * @param string $ref ex : master - * - * @return string + * @param ProjectInterface $project ex : 123456 + * @param string $filePath ex : composer.json + * @param string $ref ex : master */ public function getRawFile( ProjectInterface $project, - $filePath, - $ref - ); + string $filePath, + string $ref + ): string; } diff --git a/src/ClientOptions.php b/src/ClientOptions.php index 6a5aa25..8652f59 100644 --- a/src/ClientOptions.php +++ b/src/ClientOptions.php @@ -3,7 +3,7 @@ namespace MBO\RemoteGit; /** - * Git connection options + * Git connection options. * * @author mborne */ @@ -11,32 +11,24 @@ class ClientOptions { /** * Allows to force a given client type and avoid - * detection based on URL - * - * @var string + * detection based on URL. */ - private $type; + private string $type; /** - * Base URL (ex : https://gitlab.com) - * - * @var string + * Base URL (ex : https://gitlab.com). */ - private $url; + private string $url; /** - * Access token - * - * @var string + * Access token. */ - private $token; + private ?string $token; /** - * Bypass SSL certificate checks for self signed certificates - * - * @var bool + * Bypass SSL certificate checks for self signed certificates. */ - private $unsafeSsl; + private bool $unsafeSsl; public function __construct() { @@ -44,33 +36,25 @@ public function __construct() } /** - * True if client type is specificied - * - * @return bool + * True if client type is specified. */ - public function hasType() + public function hasType(): bool { return !empty($this->type); } /** - * Get client type - * - * @return string + * Get client type. */ - public function getType() + public function getType(): string { return $this->type; } /** - * Set client type - * - * @param string $type gitlab,github,gogs,... - * - * @return self + * Set client type (ex : github, gitlab-v4,...). */ - public function setType($type) + public function setType(string $type): self { $this->type = $type; @@ -78,21 +62,17 @@ public function setType($type) } /** - * @return string + * Get URL. */ - public function getUrl() + public function getUrl(): string { return $this->url; } /** - * Set URL - * - * @param string $url - * - * @return self + * Set URL. */ - public function setUrl($url) + public function setUrl(string $url): self { $this->url = $url; @@ -101,32 +81,24 @@ public function setUrl($url) /** * Is token defined? - * - * @return bool */ - public function hasToken() + public function hasToken(): bool { return !empty($this->token); } /** - * Get access token - * - * @return string + * Get access token. */ - public function getToken() + public function getToken(): ?string { return $this->token; } /** - * Set access token - * - * @param string $token Access token - * - * @return self + * Set access token. */ - public function setToken($token) + public function setToken(?string $token): self { $this->token = $token; @@ -134,21 +106,17 @@ public function setToken($token) } /** - * @return bool + * Is unsafeSsl. */ - public function isUnsafeSsl() + public function isUnsafeSsl(): bool { return $this->unsafeSsl; } /** - * Set unsafeSsl - * - * @param bool $unsafeSsl - * - * @return self + * Set unsafeSsl. */ - public function setUnsafeSsl($unsafeSsl) + public function setUnsafeSsl(bool $unsafeSsl): self { $this->unsafeSsl = $unsafeSsl; diff --git a/src/Exception/ClientNotFoundException.php b/src/Exception/ClientNotFoundException.php index 87bb7d0..26c7302 100644 --- a/src/Exception/ClientNotFoundException.php +++ b/src/Exception/ClientNotFoundException.php @@ -2,14 +2,15 @@ namespace MBO\RemoteGit\Exception; -use RuntimeException; - /** - * Custom exception for missing client type + * Custom exception for missing client type. */ -class ClientNotFoundException extends RuntimeException +class ClientNotFoundException extends \RuntimeException { - public function __construct($typeName, $availableTypes = []) + /** + * @param string[] $availableTypes + */ + public function __construct(string $typeName, array $availableTypes = []) { $message = sprintf("type '%s' not found in %s", $typeName, json_encode($availableTypes)); parent::__construct($message); diff --git a/src/Exception/MissingConstException.php b/src/Exception/MissingConstException.php index 3f2ec5a..1148cde 100644 --- a/src/Exception/MissingConstException.php +++ b/src/Exception/MissingConstException.php @@ -2,14 +2,12 @@ namespace MBO\RemoteGit\Exception; -use RuntimeException; - /** - * Custom exception for missing parameters + * Custom exception for missing parameters. */ -class MissingConstException extends RuntimeException +class MissingConstException extends \RuntimeException { - public function __construct($className, $constName) + public function __construct(string $className, string $constName) { $message = sprintf("Missing const '%s' on class '%s'", $constName, $className); parent::__construct($message); diff --git a/src/Exception/RawFileNotFoundException.php b/src/Exception/RawFileNotFoundException.php new file mode 100644 index 0000000..1566300 --- /dev/null +++ b/src/Exception/RawFileNotFoundException.php @@ -0,0 +1,18 @@ +projectType; } /** - * Set filter according to project type - * - * @param string $projectType Filter according to project type - * - * @return self + * Set filter according to project type. */ - public function setProjectType($projectType) + public function setProjectType(string $projectType): self { $this->projectType = $projectType; return $this; } - /** - * {@inheritdoc} - */ - public function getDescription() + public function getDescription(): string { $description = 'composer.json should exists'; if (!empty($this->projectType)) { @@ -87,10 +72,7 @@ public function getDescription() return $description; } - /** - * {@inheritdoc} - */ - public function isAccepted(ProjectInterface $project) + public function isAccepted(ProjectInterface $project): bool { try { $branch = $project->getDefaultBranch(); diff --git a/src/Filter/FilterCollection.php b/src/Filter/FilterCollection.php index b57dfc0..272c657 100644 --- a/src/Filter/FilterCollection.php +++ b/src/Filter/FilterCollection.php @@ -2,13 +2,13 @@ namespace MBO\RemoteGit\Filter; -use Psr\Log\LoggerInterface; -use MBO\RemoteGit\ProjectInterface; -use MBO\RemoteGit\ProjectFilterInterface; use MBO\RemoteGit\Helper\LoggerHelper; +use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Compose a list of filter to simplify command line integration + * Compose a list of filter to simplify command line integration. * * @author mborne */ @@ -25,8 +25,6 @@ class FilterCollection implements ProjectFilterInterface private $logger; /** - * @param LoggerInterface $logger - * * @SuppressWarnings(PHPMD.StaticAccess) */ public function __construct(LoggerInterface $logger = null) @@ -36,19 +34,16 @@ public function __construct(LoggerInterface $logger = null) } /** - * Add a filter to the collection - * - * @return void + * Add a filter to the collection. */ - public function addFilter(ProjectFilterInterface $filter) + public function addFilter(ProjectFilterInterface $filter): self { $this->filters[] = $filter; + + return $this; } - /** - * {@inheritdoc} - */ - public function getDescription() + public function getDescription(): string { $parts = []; foreach ($this->filters as $filter) { @@ -58,10 +53,7 @@ public function getDescription() return implode(PHP_EOL, $parts); } - /** - * {@inheritdoc} - */ - public function isAccepted(ProjectInterface $project) + public function isAccepted(ProjectInterface $project): bool { foreach ($this->filters as $filter) { if (!$filter->isAccepted($project)) { @@ -84,11 +76,9 @@ public function isAccepted(ProjectInterface $project) } /** - * Get filter name - * - * @return string + * Get filter name. */ - private function getFilterName(ProjectFilterInterface $filter) + private function getFilterName(ProjectFilterInterface $filter): string { $clazz = get_class($filter); $parts = explode('\\', $clazz); diff --git a/src/Filter/IgnoreRegexpFilter.php b/src/Filter/IgnoreRegexpFilter.php index 795939f..cef57dd 100644 --- a/src/Filter/IgnoreRegexpFilter.php +++ b/src/Filter/IgnoreRegexpFilter.php @@ -2,39 +2,30 @@ namespace MBO\RemoteGit\Filter; -use MBO\RemoteGit\ProjectInterface; use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; /** - * Ignore project if project.name matches a given regular expression + * Ignore project if project.name matches a given regular expression. * * @author mborne */ class IgnoreRegexpFilter implements ProjectFilterInterface { - /** - * @var string - */ - protected $ignoreRegexp; + protected string $ignoreRegexp; - public function __construct($ignoreRegexp) + public function __construct(string $ignoreRegexp) { assert(!empty($ignoreRegexp)); $this->ignoreRegexp = $ignoreRegexp; } - /** - * {@inheritdoc} - */ - public function getDescription() + public function getDescription(): string { return 'project name should not match /'.$this->ignoreRegexp.'/'; } - /** - * {@inheritdoc} - */ - public function isAccepted(ProjectInterface $project) + public function isAccepted(ProjectInterface $project): bool { return !preg_match("/$this->ignoreRegexp/", $project->getName()); } diff --git a/src/Filter/RequiredFileFilter.php b/src/Filter/RequiredFileFilter.php index aee2958..b94a272 100644 --- a/src/Filter/RequiredFileFilter.php +++ b/src/Filter/RequiredFileFilter.php @@ -2,14 +2,14 @@ namespace MBO\RemoteGit\Filter; -use Psr\Log\LoggerInterface; -use MBO\RemoteGit\ProjectInterface; -use MBO\RemoteGit\ProjectFilterInterface; use MBO\RemoteGit\ClientInterface as GitClientInterface; use MBO\RemoteGit\Helper\LoggerHelper; +use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Accept projects if git repository contains a given file in default branch + * Accept projects if git repository contains a given file in default branch. * * @author mborne */ @@ -25,6 +25,11 @@ class RequiredFileFilter implements ProjectFilterInterface */ protected $filePath; + /** + * @var LoggerInterface + */ + protected $logger; + /** * @param string $filePath * @param LoggerInterface $logger @@ -41,24 +46,22 @@ public function __construct( $this->logger = LoggerHelper::handleNull($logger); } - /** - * {@inheritdoc} - */ - public function getDescription() + public function getDescription(): string { return sprintf("File '%s' should exist in default branch", $this->filePath); } - /** - * {@inheritdoc} - */ - public function isAccepted(ProjectInterface $project) + public function isAccepted(ProjectInterface $project): bool { + $branch = $project->getDefaultBranch(); + if (is_null($branch)) { + return false; + } try { $this->gitClient->getRawFile( $project, $this->filePath, - $project->getDefaultBranch() + $branch ); return true; diff --git a/src/FindOptions.php b/src/FindOptions.php index 9e5b858..dc21a4a 100644 --- a/src/FindOptions.php +++ b/src/FindOptions.php @@ -5,40 +5,36 @@ use MBO\RemoteGit\Filter\FilterCollection; /** - * Find options to filter project listing + * Find options to filter project listing. * * @author mborne */ class FindOptions { /** - * Filter according to organizations + * Filter according to organizations. * * @var string[] */ - private $organizations = []; + private array $organizations = []; /** - * Filter according to user names + * Filter according to user names. * * @var string[] */ - private $users = []; + private array $users = []; /** - * Search string (available only for gitlab prefer the use of organizations and users) - * - * @var string + * Search string (available only for gitlab prefer the use of organizations and users). */ - private $search; + private string $search; /** * Additional filter that can't be implemented throw - * project listing API parameters - * - * @var ProjectFilterInterface + * project listing API parameters. */ - private $filter; + private ProjectFilterInterface $filter; public function __construct() { @@ -46,33 +42,29 @@ public function __construct() } /** - * True if search is defined - * - * @return bool + * True if search is defined. */ - public function hasSearch() + public function hasSearch(): bool { return !empty($this->search); } /** - * Get filter according to organizations + * Get filter according to organizations. * * @return string[] */ - public function getOrganizations() + public function getOrganizations(): array { return $this->organizations; } /** - * Set filter according to organizations + * Set filter according to organizations. * * @param string[] $organizations Filter according to organizations - * - * @return self */ - public function setOrganizations(array $organizations) + public function setOrganizations(array $organizations): self { $this->organizations = $organizations; @@ -80,23 +72,21 @@ public function setOrganizations(array $organizations) } /** - * Get filter according to user names + * Get filter according to user names. * * @return string[] */ - public function getUsers() + public function getUsers(): array { return $this->users; } /** - * Set filter according to user names + * Set filter according to user names. * * @param string[] $users Filter according to user names - * - * @return self */ - public function setUsers(array $users) + public function setUsers(array $users): self { $this->users = $users; @@ -104,23 +94,17 @@ public function setUsers(array $users) } /** - * Get search string (prefer the use of organizations and users) - * - * @return string + * Get search string (prefer the use of organizations and users). */ - public function getSearch() + public function getSearch(): string { return $this->search; } /** - * Set search string (prefer the use of organizations and users) - * - * @param string $search Search string (prefer the use of organizations and users) - * - * @return self + * Set search string (prefer the use of organizations and users). */ - public function setSearch($search) + public function setSearch(string $search): self { $this->search = $search; @@ -128,23 +112,17 @@ public function setSearch($search) } /** - * Get project listing API parameters - * - * @return ProjectFilterInterface + * Get project listing API parameters. */ - public function getFilter() + public function getFilter(): ProjectFilterInterface { return $this->filter; } /** - * Set project listing API parameters - * - * @param ProjectFilterInterface $filter project listing API parameters - * - * @return self + * Set project listing API parameters. */ - public function setFilter(ProjectFilterInterface $filter) + public function setFilter(ProjectFilterInterface $filter): self { $this->filter = $filter; diff --git a/src/Github/GithubClient.php b/src/Github/GithubClient.php index c3a3155..08a1f29 100644 --- a/src/Github/GithubClient.php +++ b/src/Github/GithubClient.php @@ -2,18 +2,18 @@ namespace MBO\RemoteGit\Github; -use Psr\Log\LoggerInterface; use GuzzleHttp\Client as GuzzleHttpClient; use MBO\RemoteGit\AbstractClient; +use MBO\RemoteGit\Exception\RawFileNotFoundException; use MBO\RemoteGit\Exception\RequiredParameterException; -use MBO\RemoteGit\ProjectInterface; use MBO\RemoteGit\FindOptions; -use MBO\RemoteGit\ProjectFilterInterface; -use MBO\RemoteGit\Helper\LoggerHelper; use MBO\RemoteGit\Http\TokenType; +use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Client implementation for github + * Client implementation for github. * * See following github docs : * @@ -42,10 +42,9 @@ class GithubClient extends AbstractClient protected $logger; /** - * Constructor with an http client and a logger + * Constructor with an http client and a logger. * * @param $httpClient http client - * @param $logger * * @SuppressWarnings(PHPMD.StaticAccess) */ @@ -53,22 +52,15 @@ public function __construct( GuzzleHttpClient $httpClient, LoggerInterface $logger = null ) { - $this->httpClient = $httpClient; - $this->logger = LoggerHelper::handleNull($logger); + parent::__construct($httpClient, $logger); } - /** - * {@inheritdoc} - */ - protected function createProject(array $rawProject) + protected function createProject(array $rawProject): GithubProject { return new GithubProject($rawProject); } - /** - * {@inheritdoc} - */ - public function find(FindOptions $options) + public function find(FindOptions $options): array { $result = []; if (empty($options->getUsers()) && empty($options->getOrganizations())) { @@ -99,7 +91,7 @@ public function find(FindOptions $options) * @return ProjectInterface[] */ protected function findByUser( - $user, + string $user, ProjectFilterInterface $projectFilter ) { /* @@ -122,12 +114,12 @@ protected function findByUser( } /** - * Find projects by username + * Find projects by username. * * @return ProjectInterface[] */ protected function findByOrg( - $org, + string $org, ProjectFilterInterface $projectFilter ) { return $this->fetchAllPages( @@ -137,17 +129,18 @@ protected function findByOrg( } /** - * Fetch all pages for a given URI + * Fetch all pages for a given URI. * - * @param string $path such as '/orgs/IGNF/repos' or '/users/mborne/repos' + * @param string $path such as '/orgs/IGNF/repos' or '/users/mborne/repos' + * @param array $extraParams * * @return ProjectInterface[] */ private function fetchAllPages( - $path, + string $path, ProjectFilterInterface $projectFilter, - $extraParams = [] - ) { + array $extraParams = [] + ): array { $result = []; for ($page = 1; $page <= self::MAX_PAGES; ++$page) { $params = array_merge($extraParams, [ @@ -164,14 +157,11 @@ private function fetchAllPages( return $result; } - /* - * @{inheritDoc} - */ public function getRawFile( ProjectInterface $project, $filePath, $ref - ) { + ): string { $metadata = $project->getRawMetadata(); $uri = str_replace( '{+path}', @@ -179,13 +169,18 @@ public function getRawFile( $metadata['contents_url'] ); $uri .= '?ref='.$ref; - $this->getLogger()->debug('GET '.$uri); - $response = $this->getHttpClient()->request('GET', $uri, [ - 'headers' => [ - 'Accept' => 'application/vnd.github.v3.raw', - ], - ]); - - return (string) $response->getBody(); + + try { + $this->getLogger()->debug('GET '.$uri); + $response = $this->getHttpClient()->request('GET', $uri, [ + 'headers' => [ + 'Accept' => 'application/vnd.github.v3.raw', + ], + ]); + + return (string) $response->getBody(); + } catch (\Exception $e) { + throw new RawFileNotFoundException($filePath, $ref, $e); + } } } diff --git a/src/Github/GithubProject.php b/src/Github/GithubProject.php index 5950899..94350bc 100644 --- a/src/Github/GithubProject.php +++ b/src/Github/GithubProject.php @@ -5,55 +5,40 @@ use MBO\RemoteGit\ProjectInterface; /** - * Project implementation for github + * Project implementation for github. * * @author mborne */ class GithubProject implements ProjectInterface { - protected $rawMetadata; - - public function __construct($rawMetadata) + /** + * @param array $rawMetadata + */ + public function __construct(private array $rawMetadata) { - $this->rawMetadata = $rawMetadata; } - /* - * @{inheritDoc} - */ - public function getId() + public function getId(): string { return $this->rawMetadata['id']; } - /* - * @{inheritDoc} - */ - public function getName() + public function getName(): string { return $this->rawMetadata['full_name']; } - /* - * @{inheritDoc} - */ - public function getDefaultBranch() + public function getDefaultBranch(): ?string { return $this->rawMetadata['default_branch']; } - /* - * @{inheritDoc} - */ - public function getHttpUrl() + public function getHttpUrl(): string { return $this->rawMetadata['clone_url']; } - /* - * @{inheritDoc} - */ - public function getRawMetadata() + public function getRawMetadata(): array { return $this->rawMetadata; } diff --git a/src/Gitlab/GitlabClient.php b/src/Gitlab/GitlabClient.php index a852794..34e6c49 100644 --- a/src/Gitlab/GitlabClient.php +++ b/src/Gitlab/GitlabClient.php @@ -2,16 +2,17 @@ namespace MBO\RemoteGit\Gitlab; -use Psr\Log\LoggerInterface; use GuzzleHttp\Client as GuzzleHttpClient; use MBO\RemoteGit\AbstractClient; -use MBO\RemoteGit\ProjectInterface; +use MBO\RemoteGit\Exception\RawFileNotFoundException; use MBO\RemoteGit\FindOptions; -use MBO\RemoteGit\ProjectFilterInterface; use MBO\RemoteGit\Http\TokenType; +use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Find gitlab projects + * Find gitlab projects. * * See following gitlab docs : * @@ -29,10 +30,7 @@ class GitlabClient extends AbstractClient public const MAX_PAGES = 10000; /** - * Constructor with an http client and a logger - * - * @param $httpClient http client - * @param $logger + * Constructor with an http client and a logger. */ public function __construct( GuzzleHttpClient $httpClient, @@ -41,18 +39,12 @@ public function __construct( parent::__construct($httpClient, $logger); } - /* - * @{inheritDoc} - */ - protected function createProject(array $rawProject) + protected function createProject(array $rawProject): GitlabProject { return new GitlabProject($rawProject); } - /* - * @{inheritDoc} - */ - public function find(FindOptions $options) + public function find(FindOptions $options): array { /* find all projects applying optional search */ if (empty($options->getUsers()) && empty($options->getOrganizations())) { @@ -77,14 +69,14 @@ public function find(FindOptions $options) } /** - * Find projects by username + * Find projects by username. * * @return ProjectInterface[] */ protected function findByUser( - $user, + string $user, ProjectFilterInterface $projectFilter - ) { + ): array { return $this->fetchAllPages( '/api/v4/users/'.urlencode($user).'/projects', [], @@ -93,14 +85,14 @@ protected function findByUser( } /** - * Find projects by group + * Find projects by group. * * @return ProjectInterface[] */ protected function findByGroup( - $group, + string $group, ProjectFilterInterface $projectFilter - ) { + ): array { return $this->fetchAllPages( '/api/v4/groups/'.urlencode($group).'/projects', [], @@ -109,7 +101,7 @@ protected function findByGroup( } /** - * Find all projects using option search + * Find all projects using option search. * * @return ProjectInterface[] */ @@ -129,16 +121,16 @@ protected function findBySearch(FindOptions $options) } /** - * Fetch all pages for a given path with query params + * Fetch all pages for a given path with query params. * - * @param string $path ex : "/api/v4/projects" - * @param array $params ex : array('search'=>'sample-composer') + * @param string $path ex : "/api/v4/projects" + * @param array $params ex : array('search'=>'sample-composer') * * @return ProjectInterface[] */ private function fetchAllPages( - $path, - array $params = [], + string $path, + array $params, ProjectFilterInterface $projectFilter ) { $result = []; @@ -157,20 +149,21 @@ private function fetchAllPages( return $result; } - /* - * @{inheritDoc} - */ public function getRawFile( ProjectInterface $project, - $filePath, - $ref - ) { + string $filePath, + string $ref + ): string { // ref : https://docs.gitlab.com/ee/api/repository_files.html#get-raw-file-from-repository $uri = '/api/v4/projects/'.$project->getId().'/repository/files/'.urlencode($filePath).'/raw'; $uri .= '?ref='.$ref; - $this->getLogger()->debug('GET '.$uri); - $response = $this->httpClient->request('GET', $uri); + try { + $this->getLogger()->debug('GET '.$uri); + $response = $this->httpClient->request('GET', $uri); - return (string) $response->getBody(); + return (string) $response->getBody(); + } catch (\Exception $e) { + throw new RawFileNotFoundException($filePath, $ref); + } } } diff --git a/src/Gitlab/GitlabProject.php b/src/Gitlab/GitlabProject.php index 874213d..0b9eece 100644 --- a/src/Gitlab/GitlabProject.php +++ b/src/Gitlab/GitlabProject.php @@ -5,39 +5,30 @@ use MBO\RemoteGit\ProjectInterface; /** - * Common project properties between different git project host (gitlab, github, etc.) + * Common project properties between different git project host (gitlab, github, etc.). * * @author mborne */ class GitlabProject implements ProjectInterface { - protected $rawMetadata; - - public function __construct($rawMetadata) + /** + * @param array $rawMetadata + */ + public function __construct(private array $rawMetadata) { - $this->rawMetadata = $rawMetadata; } - /* - * @{inheritDoc} - */ - public function getId() + public function getId(): string { return $this->rawMetadata['id']; } - /* - * @{inheritDoc} - */ - public function getName() + public function getName(): string { return $this->rawMetadata['path_with_namespace']; } - /* - * @{inheritDoc} - */ - public function getDefaultBranch() + public function getDefaultBranch(): ?string { if (!isset($this->rawMetadata['default_branch'])) { return null; @@ -46,18 +37,12 @@ public function getDefaultBranch() return $this->rawMetadata['default_branch']; } - /* - * @{inheritDoc} - */ - public function getHttpUrl() + public function getHttpUrl(): string { return $this->rawMetadata['http_url_to_repo']; } - /* - * @{inheritDoc} - */ - public function getRawMetadata() + public function getRawMetadata(): array { return $this->rawMetadata; } diff --git a/src/Gogs/GogsClient.php b/src/Gogs/GogsClient.php index e93737e..d3f738f 100644 --- a/src/Gogs/GogsClient.php +++ b/src/Gogs/GogsClient.php @@ -2,16 +2,17 @@ namespace MBO\RemoteGit\Gogs; -use Psr\Log\LoggerInterface; use GuzzleHttp\Client as GuzzleHttpClient; use MBO\RemoteGit\AbstractClient; -use MBO\RemoteGit\ProjectInterface; +use MBO\RemoteGit\Exception\RawFileNotFoundException; use MBO\RemoteGit\FindOptions; -use MBO\RemoteGit\ProjectFilterInterface; use MBO\RemoteGit\Http\TokenType; +use MBO\RemoteGit\ProjectFilterInterface; +use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Client implementation for gogs + * Client implementation for gogs and gitea. * * @author mborne */ @@ -22,9 +23,6 @@ class GogsClient extends AbstractClient public const DEFAULT_PER_PAGE = 1000; - /* - * @{inheritDoc} - */ public function __construct( GuzzleHttpClient $httpClient, LoggerInterface $logger = null @@ -32,18 +30,12 @@ public function __construct( parent::__construct($httpClient, $logger); } - /* - * @{inheritDoc} - */ - protected function createProject(array $rawProject) + protected function createProject(array $rawProject): GogsProject { return new GogsProject($rawProject); } - /* - * @{inheritDoc} - */ - public function find(FindOptions $options) + public function find(FindOptions $options): array { if (empty($options->getUsers()) && empty($options->getOrganizations())) { return $this->findByCurrentUser( @@ -69,13 +61,13 @@ public function find(FindOptions $options) } /** - * Find projects for current user + * Find projects for current user. * * @return ProjectInterface[] */ protected function findByCurrentUser( ProjectFilterInterface $projectFilter - ) { + ): array { return $this->filter( $this->getProjects( '/api/v1/user/repos', @@ -88,14 +80,14 @@ protected function findByCurrentUser( } /** - * Find projects by username + * Find projects by username. * * @return ProjectInterface[] */ protected function findByUser( - $user, + string $user, ProjectFilterInterface $projectFilter - ) { + ): array { return $this->filter( $this->getProjects( '/api/v1/users/'.$user.'/repos', @@ -108,14 +100,14 @@ protected function findByUser( } /** - * Find projects by username + * Find projects by organization. * * @return ProjectInterface[] */ protected function findByOrg( - $org, + string $org, ProjectFilterInterface $projectFilter - ) { + ): array { return $this->filter( $this->getProjects( '/api/v1/orgs/'.$org.'/repos', @@ -127,21 +119,22 @@ protected function findByOrg( ); } - /* - * @{inheritDoc} - */ public function getRawFile( ProjectInterface $project, $filePath, $ref - ) { + ): string { $uri = '/api/v1/repos/'.$project->getName().'/raw/'; $uri .= $project->getDefaultBranch(); $uri .= '/'.$filePath; - $this->getLogger()->debug('GET '.$uri); - $response = $this->getHttpClient()->request('GET', $uri); + try { + $this->getLogger()->debug('GET '.$uri); + $response = $this->getHttpClient()->request('GET', $uri); - return (string) $response->getBody(); + return (string) $response->getBody(); + } catch (\Exception $e) { + throw new RawFileNotFoundException($filePath, $ref, $e); + } } } diff --git a/src/Gogs/GogsProject.php b/src/Gogs/GogsProject.php index c58a906..6a5626d 100644 --- a/src/Gogs/GogsProject.php +++ b/src/Gogs/GogsProject.php @@ -5,55 +5,40 @@ use MBO\RemoteGit\ProjectInterface; /** - * Project implementation for github + * Project implementation for github. * * @author mborne */ class GogsProject implements ProjectInterface { - protected $rawMetadata; - - public function __construct($rawMetadata) + /** + * @param array $rawMetadata + */ + public function __construct(private array $rawMetadata) { - $this->rawMetadata = $rawMetadata; } - /* - * @{inheritDoc} - */ - public function getId() + public function getId(): string { return $this->rawMetadata['id']; } - /* - * @{inheritDoc} - */ - public function getName() + public function getName(): string { return $this->rawMetadata['full_name']; } - /* - * @{inheritDoc} - */ - public function getDefaultBranch() + public function getDefaultBranch(): ?string { return $this->rawMetadata['default_branch']; } - /* - * @{inheritDoc} - */ - public function getHttpUrl() + public function getHttpUrl(): string { return $this->rawMetadata['clone_url']; } - /* - * @{inheritDoc} - */ - public function getRawMetadata() + public function getRawMetadata(): array { return $this->rawMetadata; } diff --git a/src/Helper/ClientHelper.php b/src/Helper/ClientHelper.php index 25ea972..2aa3a4e 100644 --- a/src/Helper/ClientHelper.php +++ b/src/Helper/ClientHelper.php @@ -2,24 +2,24 @@ namespace MBO\RemoteGit\Helper; -use ReflectionClass; use MBO\RemoteGit\ClientInterface; use MBO\RemoteGit\Exception\MissingConstException; use MBO\RemoteGit\Exception\RequiredParameterException; +use ReflectionClass; /** - * Helper to inspect client classes + * Helper to inspect client classes. */ class ClientHelper { /** - * Retrieve TYPE and TOKEN_TYPE from client class + * Retrieve TYPE and TOKEN_TYPE from client class. * - * @param string $className + * @param class-string $className * * @return string[] */ - public static function getStaticProperties($className) + public static function getStaticProperties(string $className): array { $reflectionClass = new ReflectionClass($className); if (!$reflectionClass->implementsInterface(ClientInterface::class)) { diff --git a/src/Helper/LoggerHelper.php b/src/Helper/LoggerHelper.php index 5956e53..d49cc19 100644 --- a/src/Helper/LoggerHelper.php +++ b/src/Helper/LoggerHelper.php @@ -6,20 +6,16 @@ use Psr\Log\NullLogger; /** - * Helper class to simplify NullLogger management + * Helper class to simplify NullLogger management. */ class LoggerHelper { /** - * Converts null to NullLogger - * - * @param LoggerInterface $logger - * - * @return LoggerInterface + * Converts null to NullLogger. */ public static function handleNull( LoggerInterface $logger = null - ) { + ): LoggerInterface { return is_null($logger) ? new NullLogger() : $logger; } } diff --git a/src/Http/TokenType.php b/src/Http/TokenType.php index a15b170..47769fd 100644 --- a/src/Http/TokenType.php +++ b/src/Http/TokenType.php @@ -3,7 +3,7 @@ namespace MBO\RemoteGit\Http; /** - * Provides types of implementation for token + * Provides types of implementation for token. */ class TokenType { @@ -12,12 +12,11 @@ class TokenType public const AUTHORIZATION_TOKEN = 'Authorization: token {token}'; /** - * Create HTTP headers according to a tokenType + * Create HTTP headers according to a tokenType. * - * @param string $tokenType - * @param string $token + * @return array */ - public static function createHttpHeaders($tokenType, $token) + public static function createHttpHeaders(string $tokenType, ?string $token): array { if (empty($token)) { return []; diff --git a/src/Local/LocalClient.php b/src/Local/LocalClient.php index aa92d66..826dfef 100644 --- a/src/Local/LocalClient.php +++ b/src/Local/LocalClient.php @@ -2,15 +2,16 @@ namespace MBO\RemoteGit\Local; -use Psr\Log\LoggerInterface; use MBO\RemoteGit\ClientInterface; +use MBO\RemoteGit\Exception\RawFileNotFoundException; use MBO\RemoteGit\FindOptions; use MBO\RemoteGit\Helper\LoggerHelper; use MBO\RemoteGit\Http\TokenType; use MBO\RemoteGit\ProjectInterface; +use Psr\Log\LoggerInterface; /** - * Client for a local folder containing a project hierarchy + * Client for a local folder containing a project hierarchy. */ class LocalClient implements ClientInterface { @@ -18,7 +19,7 @@ class LocalClient implements ClientInterface public const TOKEN_TYPE = TokenType::NONE; /** - * Path to the root folder + * Path to the root folder. * * @var string */ @@ -30,23 +31,17 @@ class LocalClient implements ClientInterface private $logger; /** - * Create a LocalClient for a folder containing a hierarchy of git repositories - * - * @param string $rootPath - * @param LoggerInterface $logger + * Create a LocalClient for a folder containing a hierarchy of git repositories. * * @SuppressWarnings(PHPMD.StaticAccess) */ - public function __construct($rootPath, LoggerInterface $logger = null) + public function __construct(string $rootPath, LoggerInterface $logger = null) { - $this->rootPath = realpath($rootPath); + $this->rootPath = $rootPath; $this->logger = LoggerHelper::handleNull($logger); } - /** - * {@inheritdoc} - */ - public function find(FindOptions $options) + public function find(FindOptions $options): array { $projects = []; @@ -64,7 +59,7 @@ public function find(FindOptions $options) } /** - * Create a LocalProject retreiving metadata from absolute path to project + * Create a LocalProject retreiving metadata from absolute path to project. * * @param string $projectFolder * @@ -97,16 +92,16 @@ public function createLocalProject($projectFolder) * TODO use something like "git rev-parse --git-dir" to validate * folders * - * @param string $parentPath absolute path to a given folder - * - * @return string[] + * @param string $parentPath absolute path to a given folder + * @param string[] $projectFolders * * @SuppressWarnings(PHPMD.ElseExpression) */ - protected function findProjectFolders($parentPath, array &$projectFolders) + protected function findProjectFolders(string $parentPath, array &$projectFolders): void { $this->logger->debug("Checking if $parentPath is a git repository ..."); $items = scandir($parentPath); + assert(false !== $items); foreach ($items as $item) { if ('.' === $item || '..' === $item) { continue; @@ -130,28 +125,38 @@ protected function findProjectFolders($parentPath, array &$projectFolders) } } - /** - * {@inheritdoc} - */ public function getRawFile( ProjectInterface $project, - $filePath, - $ref - ) { + string $filePath, + string $ref + ): string { $cmd = sprintf( - 'cd %s ; git show %s:%s', - escapeshellarg($project->getHttpUrl()), + 'git show %s:%s', escapeshellarg($ref), escapeshellarg($filePath) ); - $this->logger->info(sprintf( - 'getRawFile(%s,%s,%s) : %s', - escapeshellarg($project->getHttpUrl()), - escapeshellarg($ref), - escapeshellarg($filePath), - $cmd - )); + $cwd = $project->getHttpUrl(); + + $pipes = []; + $proc = proc_open($cmd, [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], $pipes, $cwd); + + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + if (false === $proc || 0 !== proc_close($proc)) { + $this->logger->error(sprintf('command fails : %s', $stderr), [ + 'cmd' => $cmd, + 'cwd' => $cwd, + ]); + throw new RawFileNotFoundException($filePath, $ref); + } + + assert(false !== $stdout); - return shell_exec($cmd); + return $stdout; } } diff --git a/src/Local/LocalProject.php b/src/Local/LocalProject.php index dfc566d..29574eb 100644 --- a/src/Local/LocalProject.php +++ b/src/Local/LocalProject.php @@ -5,56 +5,38 @@ use MBO\RemoteGit\ProjectInterface; /** - * Project corresponding to a local git folder + * Project corresponding to a local git folder. */ class LocalProject implements ProjectInterface { /** - * @var string[] + * @param array $rawMetadata */ - protected $rawMetadata; - - public function __construct(array $rawMetadata) + public function __construct(private array $rawMetadata) { - $this->rawMetadata = $rawMetadata; } - /** - * {@inheritdoc} - */ - public function getId() + public function getId(): string { return $this->rawMetadata['id']; } - /** - * {@inheritdoc} - */ - public function getName() + public function getName(): string { return $this->rawMetadata['full_name']; } - /** - * {@inheritdoc} - */ - public function getDefaultBranch() + public function getDefaultBranch(): ?string { return $this->rawMetadata['head_branch']; } - /** - * {@inheritdoc} - */ - public function getHttpUrl() + public function getHttpUrl(): string { return $this->rawMetadata['full_path']; } - /** - * {@inheritdoc} - */ - public function getRawMetadata() + public function getRawMetadata(): array { return $this->rawMetadata; } diff --git a/src/ProjectFilterInterface.php b/src/ProjectFilterInterface.php index 1b49bc3..0bc5af6 100644 --- a/src/ProjectFilterInterface.php +++ b/src/ProjectFilterInterface.php @@ -3,23 +3,19 @@ namespace MBO\RemoteGit; /** - * Test if a project should be included in satis config (regexp, ) + * Test if a project should be included in satis config (regexp, ). * * @author mborne */ interface ProjectFilterInterface { /** - * Get filter description (ex : "Project should contains a composer.json file") - * - * @return string + * Get filter description (ex : "Project should contains a composer.json file"). */ - public function getDescription(); + public function getDescription(): string; /** - * Returns true if the project should be included in satis configuration - * - * @return bool + * Returns true if the project should be included in satis configuration. */ - public function isAccepted(ProjectInterface $project); + public function isAccepted(ProjectInterface $project): bool; } diff --git a/src/ProjectInterface.php b/src/ProjectInterface.php index a526d9a..11f9181 100644 --- a/src/ProjectInterface.php +++ b/src/ProjectInterface.php @@ -3,44 +3,36 @@ namespace MBO\RemoteGit; /** - * Common project properties between different git project host (gitlab, github, etc.) + * Common project properties between different git project host (gitlab, github, etc.). * * @author mborne */ interface ProjectInterface { /** - * Get project id - * - * @return string + * Get project id. */ - public function getId(); + public function getId(): string; /** - * Get project name (with namespace) - * - * @return string + * Get project name (with namespace). */ - public function getName(); + public function getName(): string; /** - * Get default branch - * - * @return string + * Get default branch. */ - public function getDefaultBranch(); + public function getDefaultBranch(): ?string; /** - * Get http url - * - * @return string + * Get http url. */ - public function getHttpUrl(); + public function getHttpUrl(): string; /** - * Get hosting service specific properties + * Get hosting service specific properties. * - * @return array + * @return array */ - public function getRawMetadata(); + public function getRawMetadata(): array; } diff --git a/tests/ClientFactoryTest.php b/tests/ClientFactoryTest.php index 71dbd20..f192cb7 100644 --- a/tests/ClientFactoryTest.php +++ b/tests/ClientFactoryTest.php @@ -4,6 +4,7 @@ use MBO\RemoteGit\ClientFactory; use MBO\RemoteGit\ClientOptions; +use MBO\RemoteGit\Exception\ClientNotFoundException; use MBO\RemoteGit\Github\GithubClient; use MBO\RemoteGit\Gitlab\GitlabClient; use MBO\RemoteGit\Gogs\GogsClient; @@ -11,7 +12,7 @@ class ClientFactoryTest extends TestCase { - public function testGetTypes() + public function testGetTypes(): void { $clientFactory = ClientFactory::getInstance(); @@ -19,21 +20,26 @@ public function testGetTypes() $this->assertCount(4, $types); } - public function testInvalidType() + public function testInvalidType(): void { $clientFactory = ClientFactory::getInstance(); - $thrown = false; + $thrown = null; try { $options = new ClientOptions(); $options->setType('missing'); $clientFactory->createGitClient($options); } catch (\Exception $e) { - $thrown = true; + $thrown = $e; } - $this->assertTrue($thrown, 'exception should be thrown'); + $this->assertNotNull($thrown, 'exception should be thrown'); + $this->assertInstanceOf(ClientNotFoundException::class, $thrown); + $this->assertStringStartsWith( + "type 'missing' not found in", + $thrown->getMessage() + ); } - public function testDetectClientType() + public function testDetectClientType(): void { $this->assertEquals( GithubClient::class, diff --git a/tests/Filter/ComposerProjectFilterTest.php b/tests/Filter/ComposerProjectFilterTest.php index 6f663a7..e54a102 100644 --- a/tests/Filter/ComposerProjectFilterTest.php +++ b/tests/Filter/ComposerProjectFilterTest.php @@ -2,23 +2,24 @@ namespace MBO\RemoteGit\Tests\Filter; -use MBO\RemoteGit\Tests\TestCase; use MBO\RemoteGit\ClientInterface; use MBO\RemoteGit\Filter\ComposerProjectFilter; +use MBO\RemoteGit\Tests\TestCase; /** - * Test ComposerProjectFilter + * Test ComposerProjectFilter. */ class ComposerProjectFilterTest extends TestCase { /** - * Test getDescription + * Test getDescription. */ - public function testGetDescription() + public function testGetDescription(): void { $gitClient = $this->getMockBuilder(ClientInterface::class) ->getMock() ; + /** @var ClientInterface $gitClient */ $filter = new ComposerProjectFilter($gitClient); $this->assertEquals( 'composer.json should exists', @@ -32,9 +33,9 @@ public function testGetDescription() } /** - * Rejected if composer.json doesn't exists + * Rejected if composer.json doesn't exists. */ - public function testMissingComposerJson() + public function testMissingComposerJson(): void { $project = $this->createMockProject('test'); @@ -46,14 +47,15 @@ public function testMissingComposerJson() ->method('getRawFile') ->willThrowException(new \Exception('404 not found')) ; + /** @var ClientInterface $gitClient */ $filter = new ComposerProjectFilter($gitClient); $this->assertFalse($filter->isAccepted($project)); } /** - * Accepted if composer.json exists + * Accepted if composer.json exists. */ - public function testComposerJsonAndTypeFilter() + public function testComposerJsonAndTypeFilter(): void { $project = $this->createMockProject('test'); @@ -70,6 +72,7 @@ public function testComposerJsonAndTypeFilter() // ->with(['composer.json']) ->willReturn(json_encode($content)) ; + /** @var ClientInterface $gitClient */ $filter = new ComposerProjectFilter($gitClient); $this->assertTrue($filter->isAccepted($project)); @@ -83,9 +86,9 @@ public function testComposerJsonAndTypeFilter() } /** - * Accepted if composer.json exists + * Accepted if composer.json exists. */ - public function testComposerJsonAndMultipleTypeFilter() + public function testComposerJsonAndMultipleTypeFilter(): void { $project = $this->createMockProject('test'); @@ -102,6 +105,7 @@ public function testComposerJsonAndMultipleTypeFilter() // ->with(['composer.json']) ->willReturn(json_encode($content)) ; + /** @var ClientInterface $gitClient */ $filter = new ComposerProjectFilter($gitClient); $this->assertTrue($filter->isAccepted($project)); $filter->setProjectType('project,library'); diff --git a/tests/Filter/FilterCollectionTest.php b/tests/Filter/FilterCollectionTest.php index 19e107d..5d6f988 100644 --- a/tests/Filter/FilterCollectionTest.php +++ b/tests/Filter/FilterCollectionTest.php @@ -2,17 +2,17 @@ namespace MBO\RemoteGit\Tests\Filter; +use MBO\RemoteGit\Filter\FilterCollection; +use MBO\RemoteGit\ProjectFilterInterface; use MBO\RemoteGit\Tests\TestCase; use Psr\Log\NullLogger; -use MBO\RemoteGit\ProjectFilterInterface; -use MBO\RemoteGit\Filter\FilterCollection; /** - * Test FilterCollection + * Test FilterCollection. */ class FilterCollectionTest extends TestCase { - public function testEmpty() + public function testEmpty(): void { $filterCollection = new FilterCollection(new NullLogger()); $project = $this->createMockProject('test'); @@ -20,13 +20,11 @@ public function testEmpty() } /** - * Create a fake project filter returning true or false - * - * @param bool $accepted + * Create a fake ProjectFilterInterface returning true or false. * * @return ProjectFilterInterface */ - private function createMockFilter($accepted, $description = 'mock') + private function createMockFilter(bool $accepted, string $description = 'mock') { $filter = $this->getMockBuilder(ProjectFilterInterface::class) ->getMock() @@ -43,7 +41,7 @@ private function createMockFilter($accepted, $description = 'mock') return $filter; } - public function testOneTrue() + public function testOneTrue(): void { $filterCollection = new FilterCollection(new NullLogger()); $filterCollection->addFilter($this->createMockFilter(true)); @@ -51,7 +49,7 @@ public function testOneTrue() $this->assertTrue($filterCollection->isAccepted($project)); } - public function testOneFalse() + public function testOneFalse(): void { $filterCollection = new FilterCollection(new NullLogger()); $filterCollection->addFilter($this->createMockFilter(false)); @@ -60,9 +58,9 @@ public function testOneFalse() } /** - * Check that isAccepted is unanymous + * Check that isAccepted is unanymous. */ - public function testTrueFalseTrue() + public function testTrueFalseTrue(): void { $filterCollection = new FilterCollection(new NullLogger()); $filterCollection->addFilter($this->createMockFilter(true, 'mock-1')); diff --git a/tests/Filter/IgnoreRegexpFilterTest.php b/tests/Filter/IgnoreRegexpFilterTest.php index ad79804..5adf839 100644 --- a/tests/Filter/IgnoreRegexpFilterTest.php +++ b/tests/Filter/IgnoreRegexpFilterTest.php @@ -2,15 +2,15 @@ namespace MBO\RemoteGit\Tests\Filter; -use MBO\RemoteGit\Tests\TestCase; use MBO\RemoteGit\Filter\IgnoreRegexpFilter; +use MBO\RemoteGit\Tests\TestCase; /** - * Test IgnoreRegexpFilter + * Test IgnoreRegexpFilter. */ class IgnoreRegexpFilterTest extends TestCase { - public function testExample() + public function testExample(): void { $filter = new IgnoreRegexpFilter('(^phpstorm|^typo3\/library)'); diff --git a/tests/Filter/RequiredFileFilterTest.php b/tests/Filter/RequiredFileFilterTest.php index 640857f..f2ba63b 100644 --- a/tests/Filter/RequiredFileFilterTest.php +++ b/tests/Filter/RequiredFileFilterTest.php @@ -2,19 +2,19 @@ namespace MBO\RemoteGit\Tests\Filter; -use MBO\RemoteGit\Tests\TestCase; use MBO\RemoteGit\ClientInterface; use MBO\RemoteGit\Filter\RequiredFileFilter; +use MBO\RemoteGit\Tests\TestCase; /** - * Test RequiredFileFilter + * Test RequiredFileFilter. */ class RequiredFileFilterTest extends TestCase { /** - * Rejected if composer.json doesn't exists + * Rejected if composer.json doesn't exists. */ - public function testRequiredFileMissing() + public function testRequiredFileMissing(): void { $project = $this->createMockProject('test'); @@ -26,14 +26,15 @@ public function testRequiredFileMissing() ->method('getRawFile') ->willThrowException(new \Exception('404 not found')) ; + /** @var ClientInterface $gitClient */ $filter = new RequiredFileFilter($gitClient, 'README.md'); $this->assertFalse($filter->isAccepted($project)); } /** - * Accepted if composer.json exists + * Accepted if composer.json exists. */ - public function testRequiredFilePresent() + public function testRequiredFilePresent(): void { $project = $this->createMockProject('test'); @@ -47,6 +48,7 @@ public function testRequiredFilePresent() // ->with(['composer.json']) ->willReturn(json_encode($content)) ; + /** @var ClientInterface $gitClient */ $filter = new RequiredFileFilter($gitClient, 'README.md'); $this->assertTrue($filter->isAccepted($project)); } diff --git a/tests/GithubClientTest.php b/tests/GithubClientTest.php index 820f03c..a018a8f 100644 --- a/tests/GithubClientTest.php +++ b/tests/GithubClientTest.php @@ -2,19 +2,19 @@ namespace MBO\RemoteGit\Tests; -use Psr\Log\NullLogger; -use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\ClientFactory; +use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\FindOptions; use MBO\RemoteGit\Github\GithubClient; use MBO\RemoteGit\Github\GithubProject; +use Psr\Log\NullLogger; class GithubClientTest extends TestCase { /** - * @return GithubClient + * Create GithubClient using GITHUB_TOKEN. */ - protected function createGithubClient() + protected function createGithubClient(): GithubClient { $token = getenv('GITHUB_TOKEN'); if (empty($token)) { @@ -28,16 +28,19 @@ protected function createGithubClient() ; /* create client */ - return ClientFactory::createClient( + $client = ClientFactory::createClient( $clientOptions, new NullLogger() ); + $this->assertInstanceOf(GithubClient::class, $client); + + return $client; } /** - * Ensure client can find mborne's projects + * Ensure client can find mborne's projects. */ - public function testUserAndOrgsRepositories() + public function testUserAndOrgsRepositories(): void { /* create client */ $client = $this->createGithubClient(); @@ -66,25 +69,31 @@ public function testUserAndOrgsRepositories() ); $project = $projectsByName['mborne/satis-gitlab']; + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); $composer = $client->getRawFile( $project, 'composer.json', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('mborne@users.noreply.github.com', $composer); + /* test getRawFile */ $testFileInSubdirectory = $client->getRawFile( $project, 'tests/TestCase.php', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('class TestCase', $testFileInSubdirectory); + + /* test getRawFile not found */ + $this->ensureThatRawFileNotFoundThrowsException($client, $project); } /** - * Ensure client can find mborne's projects with composer.json file + * Ensure client can find mborne's projects with composer.json file. */ - public function testFilterFile() + public function testFilterFile(): void { /* create client */ $client = $this->createGithubClient(); @@ -107,25 +116,27 @@ public function testFilterFile() ); $project = $projectsByName['mborne/satis-gitlab']; + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); $composer = $client->getRawFile( $project, 'composer.json', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('mborne@users.noreply.github.com', $composer); $testFileInSubdirectory = $client->getRawFile( $project, 'tests/TestCase.php', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('class TestCase', $testFileInSubdirectory); } /** - * Ensure client can find mborne's projects using _me_ + * Ensure client can find mborne's projects using _me_. */ - public function testFakeUserMe() + public function testFakeUserMe(): void { $ci = getenv('CI'); if (!empty($ci)) { diff --git a/tests/GitlabClientTest.php b/tests/GitlabClientTest.php index e1b10c5..1336ef0 100644 --- a/tests/GitlabClientTest.php +++ b/tests/GitlabClientTest.php @@ -2,19 +2,19 @@ namespace MBO\RemoteGit\Tests; -use Psr\Log\NullLogger; -use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\ClientFactory; +use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\FindOptions; use MBO\RemoteGit\Gitlab\GitlabClient; use MBO\RemoteGit\Gitlab\GitlabProject; +use Psr\Log\NullLogger; class GitlabClientTest extends TestCase { /** - * @return GitlabClient + * Create GitlabClient using GITLAB_TOKEN. */ - protected function createGitlabClient() + protected function createGitlabClient(): GitlabClient { $gitlabToken = getenv('GITLAB_TOKEN'); if (empty($gitlabToken)) { @@ -28,16 +28,19 @@ protected function createGitlabClient() ; /* create client */ - return ClientFactory::createClient( + $client = ClientFactory::createClient( $clientOptions, new NullLogger() ); + $this->assertInstanceOf(GitlabClient::class, $client); + + return $client; } /** - * Ensure client can find mborne/sample-composer by username + * Ensure client can find mborne/sample-composer by username. */ - public function testGitlabDotComByUser() + public function testGitlabDotComByUser(): void { /* create client */ $client = $this->createGitlabClient(); @@ -60,15 +63,17 @@ public function testGitlabDotComByUser() ); $project = $projectsByName['mborne/sample-composer']; + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); $composer = $client->getRawFile( $project, 'composer.json', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('mborne@users.noreply.github.com', $composer); } - public function testGitlabDotComOrgs() + public function testGitlabDotComOrgs(): void { /* create client */ $client = $this->createGitlabClient(); @@ -78,6 +83,7 @@ public function testGitlabDotComOrgs() $findOptions = new FindOptions(); $findOptions->setOrganizations(['gitlab-org']); $projects = $client->find($findOptions); + $projectsByName = []; foreach ($projects as $project) { $this->assertInstanceOf(GitlabProject::class, $project); $this->assertGettersWorks($project); @@ -90,9 +96,9 @@ public function testGitlabDotComOrgs() } /** - * Ensure client can find mborne/sample-composer with search + * Ensure client can find mborne/sample-composer with search. */ - public function testGitlabDotComSearch() + public function testGitlabDotComSearch(): void { /* create client */ $client = $this->createGitlabClient(); @@ -112,12 +118,18 @@ public function testGitlabDotComSearch() $projectsByName ); + /* test getRawFile */ $project = $projectsByName['mborne/sample-composer']; + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); $composer = $client->getRawFile( $project, 'composer.json', - $project->getDefaultBranch() + $defaultBranch ); $this->assertStringContainsString('mborne@users.noreply.github.com', $composer); + + /* test getRawFile not found */ + $this->ensureThatRawFileNotFoundThrowsException($client, $project); } } diff --git a/tests/GogsClientTest.php b/tests/GogsClientTest.php index cc0995a..ebc6363 100644 --- a/tests/GogsClientTest.php +++ b/tests/GogsClientTest.php @@ -2,10 +2,9 @@ namespace MBO\RemoteGit\Tests; -use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\ClientFactory; +use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\FindOptions; -use MBO\RemoteGit\ClientInterface; use MBO\RemoteGit\Gogs\GogsClient; use MBO\RemoteGit\Gogs\GogsProject; @@ -15,9 +14,9 @@ class GogsClientTest extends TestCase { /** - * @return ClientInterface + * Create gogs client for codes.quadtreeworld.net using QTW_TOKEN. */ - protected function createGitClient() + protected function createGitClient(): GogsClient { $gitlabToken = getenv('QTW_TOKEN'); if (empty($gitlabToken)) { @@ -32,15 +31,18 @@ protected function createGitClient() ; /* create client */ - return ClientFactory::createClient( + $client = ClientFactory::createClient( $clientOptions ); + $this->assertInstanceOf(GogsClient::class, $client); + + return $client; } /** - * Test find by current user + * Test find by current user. */ - public function testFindByCurrentUser() + public function testFindByCurrentUser(): void { /* create client */ $client = $this->createGitClient(); @@ -61,10 +63,12 @@ public function testFindByCurrentUser() } $this->assertArrayHasKey( - 'docker/docker-php', + 'docker/docker-php-sury', $projectsByName ); - $project = $projectsByName['docker/docker-php']; + + /* test getRawFile */ + $project = $projectsByName['docker/docker-php-sury']; $this->assertStringContainsString( 'FROM ', $client->getRawFile($project, 'Dockerfile', 'master') @@ -73,12 +77,15 @@ public function testFindByCurrentUser() 'ServerTokens Prod', $client->getRawFile($project, 'conf/apache-security.conf', 'master') ); + + /* test getRawFile not found */ + $this->ensureThatRawFileNotFoundThrowsException($client, $project); } /** - * Ensure client can find projects by username and organizations + * Ensure client can find projects by username and organizations. */ - public function testFindByUserAndOrgs() + public function testFindByUserAndOrgs(): void { /* create client */ $client = $this->createGitClient(); @@ -101,10 +108,10 @@ public function testFindByUserAndOrgs() } $this->assertArrayHasKey( - 'docker/docker-php', + 'docker/docker-php-sury', $projectsByName ); - $project = $projectsByName['docker/docker-php']; + $project = $projectsByName['docker/docker-php-sury']; $this->assertStringContainsString( 'FROM ', $client->getRawFile($project, 'Dockerfile', 'master') diff --git a/tests/Http/TokenTypeTest.php b/tests/Http/TokenTypeTest.php index 18892d8..1417937 100644 --- a/tests/Http/TokenTypeTest.php +++ b/tests/Http/TokenTypeTest.php @@ -2,12 +2,12 @@ namespace MBO\RemoteGit\Tests\Http; -use MBO\RemoteGit\Tests\TestCase; use MBO\RemoteGit\Http\TokenType; +use MBO\RemoteGit\Tests\TestCase; class TokenTypeTest extends TestCase { - public function testCreateHttpHeaders() + public function testCreateHttpHeaders(): void { // empty headers for null token $this->assertEquals( diff --git a/tests/LocalClientTest.php b/tests/LocalClientTest.php index 7370de9..24e971a 100644 --- a/tests/LocalClientTest.php +++ b/tests/LocalClientTest.php @@ -2,12 +2,12 @@ namespace MBO\RemoteGit\Tests; -use Psr\Log\NullLogger; -use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\ClientFactory; +use MBO\RemoteGit\ClientOptions; use MBO\RemoteGit\FindOptions; use MBO\RemoteGit\Local\LocalClient; use MBO\RemoteGit\Local\LocalProject; +use Psr\Log\NullLogger; use Symfony\Component\Filesystem\Filesystem; class LocalClientTest extends TestCase @@ -15,7 +15,7 @@ class LocalClientTest extends TestCase public const TEMP_DIR = '/tmp/remote-git-test'; /** - * Clone some projects in /tmp/remote-git-test to perform functional tests + * Clone some projects in /tmp/remote-git-test to perform functional tests. */ public static function setUpBeforeClass(): void { @@ -25,38 +25,39 @@ public static function setUpBeforeClass(): void } $fs->mkdir(self::TEMP_DIR); $fs->mkdir(self::TEMP_DIR.'/mborne'); + // non bare repository exec('cd '.self::TEMP_DIR.'/mborne && git clone https://github.com/mborne/remote-git.git'); + // bare repository exec('cd '.self::TEMP_DIR.'/mborne && git clone --bare https://github.com/mborne/satis-gitlab.git'); } /** - * Create a LocalClient for sample test directory - * - * @return LocalClient + * Create a LocalClient for sample test directory. */ - protected function createLocalClient() + protected function createLocalClient(): LocalClient { // folder containing mborne/remote-git and mborne/satis-gitlab - $rootPath = realpath(self::TEMP_DIR); - $clientOptions = new ClientOptions(); $clientOptions - ->setUrl($rootPath) + ->setUrl(self::TEMP_DIR) ; /* create client */ - return ClientFactory::createClient( + $client = ClientFactory::createClient( $clientOptions, new NullLogger() ); + $this->assertInstanceOf(LocalClient::class, $client); + + return $client; } /** - * Find all projects in test folder + * Find all projects in test folder. * * @return LocalProject[] */ - protected function findAllProjects() + protected function findAllProjects(): array { /* create client */ $client = $this->createLocalClient(); @@ -76,9 +77,9 @@ protected function findAllProjects() } /** - * Ensure that mborne/remote-git and mborne/satis-gitlab are found + * Ensure that mborne/remote-git and mborne/satis-gitlab are found. */ - public function testFindAll() + public function testFindAll(): void { $projectsByName = $this->findAllProjects(); $this->assertArrayHasKey( @@ -89,30 +90,36 @@ public function testFindAll() } /** - * Check that raw file content can be retreived from non bare repository + * Check that raw file content can be retreived from non bare repository. */ - public function testGetRawFileFromNonBareRepository() + public function testGetRawFileFromNonBareRepository(): void { + /* test getRawFile */ $client = $this->createLocalClient(); $project = $client->createLocalProject(self::TEMP_DIR.'/mborne/remote-git'); - $readmeContent = $client->getRawFile($project, 'README.md', $project->getDefaultBranch()); + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); + $readmeContent = $client->getRawFile($project, 'README.md', $defaultBranch); $this->assertStringContainsString('# mborne/remote-git', $readmeContent); - $testCaseContent = $client->getRawFile($project, 'tests/TestCase.php', $project->getDefaultBranch()); - $this->assertStringContainsString('class TestCase', $testCaseContent); + /* test getRawFile not found */ + $this->ensureThatRawFileNotFoundThrowsException($client, $project); } /** - * Check that raw file content can be retreived from bare repository + * Check that raw file content can be retreived from bare repository. */ - public function testGetRawFileFromBareRepository() + public function testGetRawFileFromBareRepository(): void { + /* test getRawFile */ $client = $this->createLocalClient(); $project = $client->createLocalProject(self::TEMP_DIR.'/mborne/satis-gitlab.git'); - $readmeContent = $client->getRawFile($project, 'composer.json', $project->getDefaultBranch()); + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); + $readmeContent = $client->getRawFile($project, 'composer.json', $defaultBranch); $this->assertStringContainsString('symfony/console', $readmeContent); - $testCaseContent = $client->getRawFile($project, 'tests/TestCase.php', $project->getDefaultBranch()); - $this->assertStringContainsString('class TestCase', $testCaseContent); + /* test getRawFile not found */ + $this->ensureThatRawFileNotFoundThrowsException($client, $project); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index a095ea8..7607b9d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,19 +2,19 @@ namespace MBO\RemoteGit\Tests; -use PHPUnit\Framework\TestCase as BaseTestCase; +use MBO\RemoteGit\ClientInterface; +use MBO\RemoteGit\Exception\RawFileNotFoundException; use MBO\RemoteGit\ProjectInterface; +use PHPUnit\Framework\TestCase as BaseTestCase; class TestCase extends BaseTestCase { /** - * Create a fake project with a given name - * - * @param string $projectName + * Create a fake project with a given name. * * @return ProjectInterface */ - protected function createMockProject($projectName) + protected function createMockProject(string $projectName) { $project = $this->getMockBuilder(ProjectInterface::class) ->getMock() @@ -32,9 +32,9 @@ protected function createMockProject($projectName) } /** - * Ensure that getter works for project + * Ensure that getter works for project. */ - protected function assertGettersWorks(ProjectInterface $project) + protected function assertGettersWorks(ProjectInterface $project): void { $this->assertNotEmpty($project->getId()); $this->assertNotEmpty($project->getName()); @@ -43,4 +43,29 @@ protected function assertGettersWorks(ProjectInterface $project) $this->assertNotEmpty($project->getHttpUrl()); $this->assertNotEmpty($project->getRawMetadata()); } + + /** + * Try to access NOT-FOUND.md on default branch and check RawFileNotFoundException. + */ + protected function ensureThatRawFileNotFoundThrowsException( + ClientInterface $client, + ProjectInterface $project + ): void { + $defaultBranch = $project->getDefaultBranch(); + $this->assertNotNull($defaultBranch); + + // try to retrieve missing file + $thrown = null; + try { + $client->getRawFile($project, 'NOT-FOUND.md', $defaultBranch); + } catch (\Exception $e) { + $thrown = $e; + } + $this->assertNotNull($thrown); + $this->assertInstanceOf(RawFileNotFoundException::class, $thrown); + $this->assertEquals( + "file 'NOT-FOUND.md' not found on branch '".$defaultBranch."'", + $thrown->getMessage() + ); + } }