diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bea9d6d..f7e118ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- New method `Redmine\Api\Attachment::fromHttpClient()` for creating the class. +- New method `Redmine\Api\CustomField::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Group::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Issue::fromHttpClient()` for creating the class. +- New method `Redmine\Api\IssueCategory::fromHttpClient()` for creating the class. +- New method `Redmine\Api\IssuePriority::fromHttpClient()` for creating the class. +- New method `Redmine\Api\IssueRelation::fromHttpClient()` for creating the class. +- New method `Redmine\Api\IssueStatus::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Membership::fromHttpClient()` for creating the class. +- New method `Redmine\Api\News::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Project::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Query::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Role::fromHttpClient()` for creating the class. +- New method `Redmine\Api\Search::fromHttpClient()` for creating the class. - Add support for PHP 8.5 - Add support for Redmine 6.1. @@ -16,6 +30,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Behaviour-driven tests are run against Redmine 6.1.0, 6.0.7, 5.1.10. +### Deprecated + +- `Redmine\Api\Attachment::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Attachment::fromHttpClient()` instead. +- Extending `Redmine\Api\Attachment` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\CustomField::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\CustomField::fromHttpClient()` instead. +- Extending `Redmine\Api\CustomField` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Group::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Group::fromHttpClient()` instead. +- Extending `Redmine\Api\Group` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Issue::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Issue::fromHttpClient()` instead. +- Extending `Redmine\Api\Issue` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\IssueCategory::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueCategory::fromHttpClient()` instead. +- Extending `Redmine\Api\IssueCategory` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\IssuePriority::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssuePriority::fromHttpClient()` instead. +- Extending `Redmine\Api\IssuePriority` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\IssueRelation::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueRelation::fromHttpClient()` instead. +- Extending `Redmine\Api\IssueRelation` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\IssueStatus::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\IssueStatus::fromHttpClient()` instead. +- Extending `Redmine\Api\IssueStatus` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Membership::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Membership::fromHttpClient()` instead. +- Extending `Redmine\Api\Membership` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\News::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\News::fromHttpClient()` instead. +- Extending `Redmine\Api\News` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Project::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Project::fromHttpClient()` instead. +- Extending `Redmine\Api\Project` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Query::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Query::fromHttpClient()` instead. +- Extending `Redmine\Api\Query` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Role::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Role::fromHttpClient()` instead. +- Extending `Redmine\Api\Role` is deprecated and will be set to final in future, create a wrapper class instead. +- `Redmine\Api\Search::__construct()` is deprecated and will be set to private in future, use `\Redmine\Api\Search::fromHttpClient()` instead. +- Extending `Redmine\Api\Search` is deprecated and will be set to final in future, create a wrapper class instead. + ### Removed - Drop support for Redmine 5.0.x. diff --git a/composer.json b/composer.json index 4fd3296e..2171762c 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,10 @@ "coverage": "phpunit -c tests/phpunit-coverage.xml.dist --coverage-html=\".phpunit.cache/code-coverage\"", "phpstan": "phpstan analyze --memory-limit 512M --configuration .phpstan.neon", "phpunit": "phpunit -c phpunit.xml.dist", + "rectify": [ + "@rector", + "@codestyle" + ], "rector": "rector -c .rector.php", "test": [ "@phpstan", diff --git a/src/Redmine/Api/Attachment.php b/src/Redmine/Api/Attachment.php index fb9fd8c4..59cba24b 100644 --- a/src/Redmine/Api/Attachment.php +++ b/src/Redmine/Api/Attachment.php @@ -2,8 +2,10 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -17,6 +19,37 @@ */ class Attachment extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Attachment::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * Get extended information about an attachment. * diff --git a/src/Redmine/Api/CustomField.php b/src/Redmine/Api/CustomField.php index 4460e43f..53d3497d 100644 --- a/src/Redmine/Api/CustomField.php +++ b/src/Redmine/Api/CustomField.php @@ -2,9 +2,11 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * Listing custom fields. @@ -15,6 +17,11 @@ */ class CustomField extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -25,6 +32,32 @@ class CustomField extends AbstractApi */ private ?array $customFieldNames = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see CustomField::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List custom fields. * diff --git a/src/Redmine/Api/Group.php b/src/Redmine/Api/Group.php index 08ee0164..cf20a41a 100644 --- a/src/Redmine/Api/Group.php +++ b/src/Redmine/Api/Group.php @@ -2,10 +2,12 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\MissingParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -21,6 +23,11 @@ */ class Group extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -31,6 +38,32 @@ class Group extends AbstractApi */ private ?array $groupNames = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Group::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List groups. * diff --git a/src/Redmine/Api/Issue.php b/src/Redmine/Api/Issue.php index 8e40cb3b..a6a1944c 100644 --- a/src/Redmine/Api/Issue.php +++ b/src/Redmine/Api/Issue.php @@ -2,11 +2,13 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Client\NativeCurlClient; use Redmine\Client\Psr18Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -47,6 +49,11 @@ class Issue extends AbstractApi */ public const PRIO_IMMEDIATE = 5; + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|IssueCategory */ @@ -72,6 +79,32 @@ class Issue extends AbstractApi */ private $userApi = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Issue::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List issues. * @@ -489,7 +522,7 @@ private function getIssueCategoryApi() /** @var IssueCategory */ $issueCategoryApi = $this->client->getApi('issue_category'); } else { - $issueCategoryApi = new IssueCategory($this->getHttpClient()); + $issueCategoryApi = IssueCategory::fromHttpClient($this->getHttpClient()); } $this->issueCategoryApi = $issueCategoryApi; @@ -508,7 +541,7 @@ private function getIssueStatusApi() /** @var IssueStatus */ $issueStatusApi = $this->client->getApi('issue_status'); } else { - $issueStatusApi = new IssueStatus($this->getHttpClient()); + $issueStatusApi = IssueStatus::fromHttpClient($this->getHttpClient()); } $this->issueStatusApi = $issueStatusApi; @@ -527,7 +560,7 @@ private function getProjectApi() /** @var Project */ $projectApi = $this->client->getApi('project'); } else { - $projectApi = new Project($this->getHttpClient()); + $projectApi = Project::fromHttpClient($this->getHttpClient()); } $this->projectApi = $projectApi; diff --git a/src/Redmine/Api/IssueCategory.php b/src/Redmine/Api/IssueCategory.php index 4dc73b66..fc225663 100644 --- a/src/Redmine/Api/IssueCategory.php +++ b/src/Redmine/Api/IssueCategory.php @@ -2,11 +2,13 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\MissingParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -22,6 +24,11 @@ */ class IssueCategory extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -32,6 +39,32 @@ class IssueCategory extends AbstractApi */ private array $issueCategoriesNames = []; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see IssueCategory::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List issue categories for a given project. * diff --git a/src/Redmine/Api/IssuePriority.php b/src/Redmine/Api/IssuePriority.php index 7bcf505f..e346dc82 100644 --- a/src/Redmine/Api/IssuePriority.php +++ b/src/Redmine/Api/IssuePriority.php @@ -2,9 +2,11 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * Listing issue priorities. @@ -15,6 +17,37 @@ */ class IssuePriority extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see IssuePriority::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List issue priorities. * diff --git a/src/Redmine/Api/IssueRelation.php b/src/Redmine/Api/IssueRelation.php index 4b53cb93..4b77269a 100644 --- a/src/Redmine/Api/IssueRelation.php +++ b/src/Redmine/Api/IssueRelation.php @@ -2,10 +2,12 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\MissingParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; @@ -18,6 +20,37 @@ */ class IssueRelation extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see IssueRelation::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List relations of the given $issueId. * diff --git a/src/Redmine/Api/IssueStatus.php b/src/Redmine/Api/IssueStatus.php index 6322de7b..b92c8524 100644 --- a/src/Redmine/Api/IssueStatus.php +++ b/src/Redmine/Api/IssueStatus.php @@ -2,9 +2,11 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * Listing issue statuses. @@ -15,6 +17,11 @@ */ class IssueStatus extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -25,6 +32,32 @@ class IssueStatus extends AbstractApi */ private ?array $issueStatusNames = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see IssueStatus::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List issue statuses. * diff --git a/src/Redmine/Api/Membership.php b/src/Redmine/Api/Membership.php index f71fc16f..6cdda7ae 100644 --- a/src/Redmine/Api/Membership.php +++ b/src/Redmine/Api/Membership.php @@ -2,11 +2,13 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\MissingParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\XmlSerializer; use SimpleXMLElement; @@ -20,6 +22,37 @@ */ class Membership extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Membership::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List memberships for a given project. * diff --git a/src/Redmine/Api/News.php b/src/Redmine/Api/News.php index 21e964df..94815782 100644 --- a/src/Redmine/Api/News.php +++ b/src/Redmine/Api/News.php @@ -2,10 +2,12 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * @see http://www.redmine.org/projects/redmine/wiki/Rest_News @@ -14,6 +16,37 @@ */ class News extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see News::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List news for a given project. * diff --git a/src/Redmine/Api/Project.php b/src/Redmine/Api/Project.php index 60511f07..1d0a2f82 100755 --- a/src/Redmine/Api/Project.php +++ b/src/Redmine/Api/Project.php @@ -3,10 +3,12 @@ namespace Redmine\Api; use InvalidArgumentException; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\MissingParameterException; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -22,6 +24,11 @@ */ class Project extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -32,6 +39,32 @@ class Project extends AbstractApi */ private ?array $projectNames = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Project::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List projects. * diff --git a/src/Redmine/Api/Query.php b/src/Redmine/Api/Query.php index f21ef308..94b8bc00 100644 --- a/src/Redmine/Api/Query.php +++ b/src/Redmine/Api/Query.php @@ -2,9 +2,11 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * Custom queries retrieval. @@ -15,6 +17,37 @@ */ class Query extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Query::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * Returns the list of all custom queries visible by the user (public and private queries) for all projects. * diff --git a/src/Redmine/Api/Role.php b/src/Redmine/Api/Role.php index 3f95336d..fb8c99b7 100644 --- a/src/Redmine/Api/Role.php +++ b/src/Redmine/Api/Role.php @@ -2,9 +2,11 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; use Redmine\Http\HttpFactory; use Redmine\Serializer\JsonSerializer; @@ -17,6 +19,11 @@ */ class Role extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + /** * @var null|array */ @@ -27,6 +34,32 @@ class Role extends AbstractApi */ private ?array $roleNames = null; + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Role::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * List roles. * diff --git a/src/Redmine/Api/Search.php b/src/Redmine/Api/Search.php index 6a6f612b..2dc40b87 100644 --- a/src/Redmine/Api/Search.php +++ b/src/Redmine/Api/Search.php @@ -2,15 +2,48 @@ namespace Redmine\Api; +use Redmine\Client\Client; use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Http\HttpClient; /** * @see http://www.redmine.org/projects/redmine/wiki/Rest_Search */ class Search extends AbstractApi { + final public static function fromHttpClient(HttpClient $httpClient): self + { + return new self($httpClient, true); + } + + /** + * @deprecated v2.9.0 Use fromHttpClient() instead. + * @see Search::fromHttpClient() + * + * @param Client|HttpClient $client + */ + public function __construct($client/*, bool $privatelyCalled = false*/) + { + $privatelyCalled = (func_num_args() > 1) ? func_get_arg(1) : false; + + if ($privatelyCalled === true) { + parent::__construct($client); + + return; + } + + if (static::class !== self::class) { + $className = (new \ReflectionClass($this))->isAnonymous() ? '' : ' in `' . static::class . '`'; + @trigger_error('Class `' . self::class . '` will declared as final in v3.0.0, stop extending it' . $className . '.', E_USER_DEPRECATED); + } else { + @trigger_error('Method `' . __METHOD__ . '()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `' . self::class . '::fromHttpClient()` instead.', E_USER_DEPRECATED); + } + + parent::__construct($client); + } + /** * list search results by Query. * diff --git a/tests/Unit/Api/Attachment/DownloadTest.php b/tests/Unit/Api/Attachment/DownloadTest.php index 87301202..cdcd6c9d 100644 --- a/tests/Unit/Api/Attachment/DownloadTest.php +++ b/tests/Unit/Api/Attachment/DownloadTest.php @@ -31,7 +31,7 @@ public function testDownloadReturnsCorrectResponse($id, string $expectedPath, in ); // Create the object under test - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->download($id)); diff --git a/tests/Unit/Api/Attachment/FromHttpClientTest.php b/tests/Unit/Api/Attachment/FromHttpClientTest.php new file mode 100644 index 00000000..9387bcfc --- /dev/null +++ b/tests/Unit/Api/Attachment/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Attachment::fromHttpClient($httpClient); + + $this->assertInstanceOf(Attachment::class, $api); + } +} diff --git a/tests/Unit/Api/Attachment/RemoveTest.php b/tests/Unit/Api/Attachment/RemoveTest.php index 0f21a33c..4384284e 100644 --- a/tests/Unit/Api/Attachment/RemoveTest.php +++ b/tests/Unit/Api/Attachment/RemoveTest.php @@ -27,7 +27,7 @@ public function testRemoveReturnsString(): void ], ); - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); $this->assertSame('', $api->remove(5)); } diff --git a/tests/Unit/Api/Attachment/ShowTest.php b/tests/Unit/Api/Attachment/ShowTest.php index 9c51b64b..fed26670 100644 --- a/tests/Unit/Api/Attachment/ShowTest.php +++ b/tests/Unit/Api/Attachment/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($id, string $expectedPath, string ); // Create the object under test - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($id)); diff --git a/tests/Unit/Api/Attachment/UpdateTest.php b/tests/Unit/Api/Attachment/UpdateTest.php index 02923902..9c3535af 100644 --- a/tests/Unit/Api/Attachment/UpdateTest.php +++ b/tests/Unit/Api/Attachment/UpdateTest.php @@ -32,7 +32,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $params, string ); // Create the object under test - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->update($id, $params)); @@ -69,7 +69,7 @@ public function testUpdateThrowsUnexpectedResponseException(): void ], ); - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Attachment/UploadTest.php b/tests/Unit/Api/Attachment/UploadTest.php index 6f1d7540..5183be45 100644 --- a/tests/Unit/Api/Attachment/UploadTest.php +++ b/tests/Unit/Api/Attachment/UploadTest.php @@ -31,7 +31,7 @@ public function testUploadReturnsCorrectResponse(string $attachment, array $para ); // Create the object under test - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->upload($attachment, $params)); diff --git a/tests/Unit/Api/AttachmentTest.php b/tests/Unit/Api/AttachmentTest.php index a1ab9018..55969996 100644 --- a/tests/Unit/Api/AttachmentTest.php +++ b/tests/Unit/Api/AttachmentTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\Attachment; use Redmine\Client\Client; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -14,22 +16,76 @@ #[CoversClass(Attachment::class)] class AttachmentTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Attachment` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Attachment {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Attachment::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Attachment::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Attachment($this->createStub(HttpClient::class)); + } + + public function testLastCallFailedWithoutPreviousRequestReturnsTrue(): void + { + $api = Attachment::fromHttpClient($this->createStub(HttpClient::class)); + + // Perform the tests + $this->assertTrue($api->lastCallFailed()); + } + /** * Test lastCallFailed(). * * @dataProvider responseCodeProvider */ #[DataProvider('responseCodeProvider')] - public function testLastCallFailedTrue(int $responseCode, bool $hasFailed): void + public function testLastCallFailedReturnsCorrectValue(int $responseCode, bool $hasFailed): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('getLastResponseStatusCode') - ->willReturn($responseCode); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/attachments/1.json', + 'application/json', + '', + $responseCode, + '', + '', + ], + ); // Create the object under test - $api = new Attachment($client); + $api = Attachment::fromHttpClient($client); + $api->show(1); // Perform the tests $this->assertSame($hasFailed, $api->lastCallFailed()); diff --git a/tests/Unit/Api/CustomField/FromHttpClientTest.php b/tests/Unit/Api/CustomField/FromHttpClientTest.php new file mode 100644 index 00000000..af2e7e41 --- /dev/null +++ b/tests/Unit/Api/CustomField/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = CustomField::fromHttpClient($httpClient); + + $this->assertInstanceOf(CustomField::class, $api); + } +} diff --git a/tests/Unit/Api/CustomField/ListNamesTest.php b/tests/Unit/Api/CustomField/ListNamesTest.php index 36153176..d0fcb59f 100644 --- a/tests/Unit/Api/CustomField/ListNamesTest.php +++ b/tests/Unit/Api/CustomField/ListNamesTest.php @@ -33,7 +33,7 @@ public function testListNamesReturnsCorrectResponse(string $expectedPath, int $r ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNames()); @@ -98,7 +98,7 @@ public function testListNamesCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'CustomField 1'], $api->listNames()); diff --git a/tests/Unit/Api/CustomField/ListTest.php b/tests/Unit/Api/CustomField/ListTest.php index 4498144e..c1a4fdb3 100644 --- a/tests/Unit/Api/CustomField/ListTest.php +++ b/tests/Unit/Api/CustomField/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\CustomField; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(CustomField::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedResponse = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/custom_fields.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedResponse = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/custom_fields.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->list($allParameters)); @@ -74,23 +74,39 @@ public function testListWithHighLimitParametersReturnsResponse(): void 'items' => [], ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(3)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(3)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(3)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=100&offset=0', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/custom_fields.json?limit=100&offset=100', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/custom_fields.json?limit=50&offset=200', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->list($allParameters)); @@ -108,23 +124,21 @@ public function testListCallsEndpointUntilOffsetIsHigherThanTotalCount(): void 'items' => [], ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=100&offset=0', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($returnDataSet, $api->list($allParameters)); @@ -132,21 +146,21 @@ public function testListCallsEndpointUntilOffsetIsHigherThanTotalCount(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/custom_fields.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/CustomFieldTest.php b/tests/Unit/Api/CustomFieldTest.php index 39811bda..9b4cae8a 100644 --- a/tests/Unit/Api/CustomFieldTest.php +++ b/tests/Unit/Api/CustomFieldTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\CustomField; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(CustomField::class)] class CustomFieldTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\CustomField` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends CustomField {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\CustomField::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\CustomField::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new CustomField($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new CustomField(MockClient::create()); + $api = CustomField::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/custom_fields.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,23 +124,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedResponse = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringContains('not-used'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all($allParameters)); @@ -121,23 +157,39 @@ public function testAllReturnsClientGetResponseWithHighLimit(): void 'items' => [], ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(3)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(3)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(3)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=100&offset=0', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/custom_fields.json?limit=100&offset=100', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/custom_fields.json?limit=50&offset=200', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all($allParameters)); @@ -152,23 +204,21 @@ public function testAllCallsEndpointUntilOffsetIsHigherThanTotalCount(): void $response = '{"limit":"100","offset":"10","total_count":"5","items":[]}'; $allParameters = ['limit' => 250]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json?limit=100&offset=0', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $retrievedDataSet = $api->all($allParameters); @@ -189,23 +239,21 @@ public function testListingReturnsNameIdArray(): void 'CustomField 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -223,23 +271,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'CustomField 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -258,23 +304,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'CustomField 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(true)); @@ -286,15 +339,22 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"custom_fields":[{"id":1,"name":"CustomField 1"},{"id":5,"name":"CustomField 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"custom_fields":[{"id":1,"name":"CustomField 1"},{"id":5,"name":"CustomField 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -321,23 +381,21 @@ public function testGetIdByNameMakesGetRequest(): void // Test values $response = '{"custom_fields":[{"id":5,"name":"CustomField 5"}]}'; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/custom_fields.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new CustomField($client); + $api = CustomField::fromHttpClient($client); // Perform the tests $this->assertFalse($api->getIdByName('CustomField 1')); @@ -346,15 +404,22 @@ public function testGetIdByNameMakesGetRequest(): void public function testGetIdByNameTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"custom_fields":[{"id":1,"name":"CustomField 1"},{"id":5,"name":"CustomField 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); - - $api = new CustomField($client); + $response = '{"custom_fields":[{"id":1,"name":"CustomField 1"},{"id":5,"name":"CustomField 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/custom_fields.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = CustomField::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( diff --git a/tests/Unit/Api/Group/AddUserTest.php b/tests/Unit/Api/Group/AddUserTest.php index cfd15448..3917ed64 100644 --- a/tests/Unit/Api/Group/AddUserTest.php +++ b/tests/Unit/Api/Group/AddUserTest.php @@ -32,7 +32,7 @@ public function testAddUserReturnsCorrectResponse(int $groupId, int $userId, str ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $return = $api->addUser($groupId, $userId); @@ -74,7 +74,7 @@ public function testAddUserReturnsEmptyString(): void ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $return = $api->addUser(1, 2); diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php index 13ae0b3c..2c3621c6 100644 --- a/tests/Unit/Api/Group/CreateTest.php +++ b/tests/Unit/Api/Group/CreateTest.php @@ -36,7 +36,7 @@ public function testCreateReturnsCorrectResponse(array $parameters, string $expe ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $return = $api->create($parameters); @@ -123,7 +123,7 @@ public function testCreateReturnsEmptyString(): void ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $return = $api->create(['name' => 'Group Name']); @@ -140,7 +140,7 @@ public function testCreateThrowsExceptionIfNameIsMissing(): void $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `name`'); diff --git a/tests/Unit/Api/Group/FromHttpClientTest.php b/tests/Unit/Api/Group/FromHttpClientTest.php new file mode 100644 index 00000000..ceed94b1 --- /dev/null +++ b/tests/Unit/Api/Group/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Group::fromHttpClient($httpClient); + + $this->assertInstanceOf(Group::class, $api); + } +} diff --git a/tests/Unit/Api/Group/ListNamesTest.php b/tests/Unit/Api/Group/ListNamesTest.php index 14b413b7..91c5a456 100644 --- a/tests/Unit/Api/Group/ListNamesTest.php +++ b/tests/Unit/Api/Group/ListNamesTest.php @@ -33,7 +33,7 @@ public function testListNamesReturnsCorrectResponse(string $expectedPath, int $r ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNames()); @@ -98,7 +98,7 @@ public function testListNamesCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'Group 1'], $api->listNames()); diff --git a/tests/Unit/Api/Group/ListTest.php b/tests/Unit/Api/Group/ListTest.php index 7d5c4d17..b082ef02 100644 --- a/tests/Unit/Api/Group/ListTest.php +++ b/tests/Unit/Api/Group/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\Group; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Group::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/groups.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListeWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/groups.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,21 @@ public function testListeWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/groups.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Group/RemoveTest.php b/tests/Unit/Api/Group/RemoveTest.php index d89205b8..6f9a18e9 100644 --- a/tests/Unit/Api/Group/RemoveTest.php +++ b/tests/Unit/Api/Group/RemoveTest.php @@ -27,7 +27,7 @@ public function testRemoveReturnsString(): void ], ); - $api = new Group($client); + $api = Group::fromHttpClient($client); $this->assertSame('', $api->remove(5)); } diff --git a/tests/Unit/Api/Group/RemoveUserTest.php b/tests/Unit/Api/Group/RemoveUserTest.php index fb9a94b4..cb9c5bea 100644 --- a/tests/Unit/Api/Group/RemoveUserTest.php +++ b/tests/Unit/Api/Group/RemoveUserTest.php @@ -27,7 +27,7 @@ public function testRemoveUserReturnsString(): void ], ); - $api = new Group($client); + $api = Group::fromHttpClient($client); $this->assertSame('', $api->removeUser(5, 10)); } diff --git a/tests/Unit/Api/Group/ShowTest.php b/tests/Unit/Api/Group/ShowTest.php index 0fbc4275..21b3822c 100644 --- a/tests/Unit/Api/Group/ShowTest.php +++ b/tests/Unit/Api/Group/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($groupId, array $params, string $ ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($groupId, $params)); diff --git a/tests/Unit/Api/Group/UpdateTest.php b/tests/Unit/Api/Group/UpdateTest.php index d039e3ba..15014e77 100644 --- a/tests/Unit/Api/Group/UpdateTest.php +++ b/tests/Unit/Api/Group/UpdateTest.php @@ -33,7 +33,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $parameters, str ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update($id, $parameters)); diff --git a/tests/Unit/Api/GroupTest.php b/tests/Unit/Api/GroupTest.php index ba05217d..5e87bfd9 100644 --- a/tests/Unit/Api/GroupTest.php +++ b/tests/Unit/Api/GroupTest.php @@ -8,8 +8,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Group; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -17,12 +17,50 @@ #[CoversClass(Group::class)] class GroupTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Group` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Group {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Group::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Group::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Group($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new Group(MockClient::create()); + $api = Group::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -49,21 +87,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/groups.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -88,26 +126,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/groups.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); @@ -118,15 +151,22 @@ public function testAllReturnsClientGetResponseWithParameters(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"groups":[{"id":1,"name":"Group 1"},{"id":5,"name":"Group 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"groups":[{"id":1,"name":"Group 1"},{"id":5,"name":"Group 5"}]}'; - $api = new Group($client); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = Group::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -157,23 +197,21 @@ public function testListingReturnsNameIdArray(): void 'Group 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/groups.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -191,23 +229,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'Group 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/groups.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -226,23 +262,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'Group 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/groups.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/groups.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Group($client); + $api = Group::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(true)); diff --git a/tests/Unit/Api/Issue/AddNoteToIssueTest.php b/tests/Unit/Api/Issue/AddNoteToIssueTest.php index af5a9dc0..99fff46b 100644 --- a/tests/Unit/Api/Issue/AddNoteToIssueTest.php +++ b/tests/Unit/Api/Issue/AddNoteToIssueTest.php @@ -33,7 +33,7 @@ public function testAddNoteToIssueReturnsCorrectResponse(int $id, string $note, ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->addNoteToIssue($id, $note, $isPrivate)); diff --git a/tests/Unit/Api/Issue/AddWatcherTest.php b/tests/Unit/Api/Issue/AddWatcherTest.php index 868807e1..c92ddc25 100644 --- a/tests/Unit/Api/Issue/AddWatcherTest.php +++ b/tests/Unit/Api/Issue/AddWatcherTest.php @@ -32,7 +32,7 @@ public function testAddWatcherReturnsCorrectResponse(int $issueId, int $watcherU ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $return = $api->addWatcher($issueId, $watcherUserId); @@ -74,7 +74,7 @@ public function testAddWatcherReturnsEmptyString(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $return = $api->addWatcher(1, 2); diff --git a/tests/Unit/Api/Issue/AttachManyTest.php b/tests/Unit/Api/Issue/AttachManyTest.php index f88bf247..2e9631ab 100644 --- a/tests/Unit/Api/Issue/AttachManyTest.php +++ b/tests/Unit/Api/Issue/AttachManyTest.php @@ -31,7 +31,7 @@ public function testAttachManyReturnsCorrectResponse(int $issueId, array $parame ); // AttachMany the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->attachMany($issueId, $parameters)); diff --git a/tests/Unit/Api/Issue/AttachTest.php b/tests/Unit/Api/Issue/AttachTest.php index 5ccd2641..0949289c 100644 --- a/tests/Unit/Api/Issue/AttachTest.php +++ b/tests/Unit/Api/Issue/AttachTest.php @@ -31,7 +31,7 @@ public function testAttachReturnsCorrectResponse(int $issueId, array $parameters ); // Attach the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->attach($issueId, $parameters)); diff --git a/tests/Unit/Api/Issue/CreateTest.php b/tests/Unit/Api/Issue/CreateTest.php index 09e1e1b9..edb2648f 100644 --- a/tests/Unit/Api/Issue/CreateTest.php +++ b/tests/Unit/Api/Issue/CreateTest.php @@ -32,7 +32,7 @@ public function testCreateReturnsCorrectResponse(array $parameters, string $expe ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $return = $api->create($parameters); @@ -248,7 +248,7 @@ public function testCreateReturnsEmptyString(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $return = $api->create([]); @@ -281,7 +281,7 @@ public function testCreateWithHttpClientRetrievesIssueStatusId(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create(['status' => 'Status Name']); @@ -318,7 +318,7 @@ public function testCreateWithHttpClientRetrievesProjectId(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create(['project' => 'Project Name']); @@ -355,7 +355,7 @@ public function testCreateWithHttpClientRetrievesIssueCategoryId(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create(['project_id' => 3, 'category' => 'Category Name']); @@ -392,7 +392,7 @@ public function testCreateWithHttpClientRetrievesTrackerId(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create(['tracker' => 'Tracker Name']); @@ -429,7 +429,7 @@ public function testCreateWithHttpClientRetrievesUserId(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create(['assigned_to' => 'user_6', 'author' => 'user_5']); @@ -524,7 +524,7 @@ public function testCreateWithClientCleansParameters(): void ]; // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $xmlElement = $api->create($parameters); diff --git a/tests/Unit/Api/Issue/FromHttpClientTest.php b/tests/Unit/Api/Issue/FromHttpClientTest.php new file mode 100644 index 00000000..ba0d1eb3 --- /dev/null +++ b/tests/Unit/Api/Issue/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Issue::fromHttpClient($httpClient); + + $this->assertInstanceOf(Issue::class, $api); + } +} diff --git a/tests/Unit/Api/Issue/ListTest.php b/tests/Unit/Api/Issue/ListTest.php index 26637a84..22b62068 100644 --- a/tests/Unit/Api/Issue/ListTest.php +++ b/tests/Unit/Api/Issue/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\Issue; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Issue::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issues.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issues.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,21 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issues.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Issue/RemoveTest.php b/tests/Unit/Api/Issue/RemoveTest.php index effe5f99..ba5d7015 100644 --- a/tests/Unit/Api/Issue/RemoveTest.php +++ b/tests/Unit/Api/Issue/RemoveTest.php @@ -31,7 +31,7 @@ public function testRemoveReturnsCorrectResponse(int $issueId, string $expectedP ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->remove($issueId)); diff --git a/tests/Unit/Api/Issue/RemoveWatcherTest.php b/tests/Unit/Api/Issue/RemoveWatcherTest.php index 677ef505..baa2d41a 100644 --- a/tests/Unit/Api/Issue/RemoveWatcherTest.php +++ b/tests/Unit/Api/Issue/RemoveWatcherTest.php @@ -31,7 +31,7 @@ public function testRemoveWatcherReturnsCorrectResponse(int $issueId, int $watch ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->removeWatcher($issueId, $watcherUserId)); diff --git a/tests/Unit/Api/Issue/SetIssueStatusTest.php b/tests/Unit/Api/Issue/SetIssueStatusTest.php index a4439f64..6cb24da5 100644 --- a/tests/Unit/Api/Issue/SetIssueStatusTest.php +++ b/tests/Unit/Api/Issue/SetIssueStatusTest.php @@ -35,7 +35,7 @@ public function testSetIssueStatusReturnsCorrectResponse(): void ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->setIssueStatus(5, 'Status Name')); diff --git a/tests/Unit/Api/Issue/ShowTest.php b/tests/Unit/Api/Issue/ShowTest.php index b28a47a2..a1621921 100644 --- a/tests/Unit/Api/Issue/ShowTest.php +++ b/tests/Unit/Api/Issue/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($issueId, array $params, string $ ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($issueId, $params)); diff --git a/tests/Unit/Api/Issue/UpdateTest.php b/tests/Unit/Api/Issue/UpdateTest.php index f61c20d4..a352fd51 100644 --- a/tests/Unit/Api/Issue/UpdateTest.php +++ b/tests/Unit/Api/Issue/UpdateTest.php @@ -33,7 +33,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $parameters, str ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update($id, $parameters)); @@ -194,7 +194,7 @@ public function testUpdateCleansParameters(): void ]; // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update(70, $parameters)); diff --git a/tests/Unit/Api/IssueCategory/CreateTest.php b/tests/Unit/Api/IssueCategory/CreateTest.php index ecd48568..4900c31f 100644 --- a/tests/Unit/Api/IssueCategory/CreateTest.php +++ b/tests/Unit/Api/IssueCategory/CreateTest.php @@ -34,7 +34,7 @@ public function testCreateReturnsCorrectResponse($identifier, array $parameters, ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $return = $api->create($identifier, $parameters); @@ -89,7 +89,7 @@ public function testCreateReturnsEmptyString(): void ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $return = $api->create(5, ['name' => 'Test Category']); @@ -103,7 +103,7 @@ public function testCreateThrowsExceptionWithEmptyParameters(): void $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `name'); @@ -122,7 +122,7 @@ public function testCreateThrowsExceptionIfMandatoyParametersAreMissing(array $p $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `name'); diff --git a/tests/Unit/Api/IssueCategory/FromHttpClientTest.php b/tests/Unit/Api/IssueCategory/FromHttpClientTest.php new file mode 100644 index 00000000..bb51858a --- /dev/null +++ b/tests/Unit/Api/IssueCategory/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = IssueCategory::fromHttpClient($httpClient); + + $this->assertInstanceOf(IssueCategory::class, $api); + } +} diff --git a/tests/Unit/Api/IssueCategory/ListByProjectTest.php b/tests/Unit/Api/IssueCategory/ListByProjectTest.php index 356ec2ff..53c8b5c2 100644 --- a/tests/Unit/Api/IssueCategory/ListByProjectTest.php +++ b/tests/Unit/Api/IssueCategory/ListByProjectTest.php @@ -6,10 +6,10 @@ use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; use Redmine\Api\IssueCategory; -use Redmine\Client\Client; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\UnexpectedResponseException; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\TestDataProvider; #[CoversClass(IssueCategory::class)] @@ -22,21 +22,21 @@ public function testListByProjectWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/5/issue_categories.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject($projectId)); @@ -50,21 +50,21 @@ public function testListByProjectWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/project-slug/issue_categories.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/project-slug/issue_categories.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject($projectId, $parameters)); @@ -76,7 +76,7 @@ public function testListByProjectWithParametersReturnsResponse(): void #[DataProviderExternal(TestDataProvider::class, 'getInvalidProjectIdentifiers')] public function testListByProjectWithWrongProjectIdentifierThrowsException($projectIdentifier): void { - $api = new IssueCategory(MockClient::create()); + $api = IssueCategory::fromHttpClient($this->createStub(HttpClient::class)); $this->expectException(InvalidParameterException::class); $this->expectExceptionMessage('Redmine\Api\IssueCategory::listByProject(): Argument #1 ($projectIdentifier) must be of type int or string'); @@ -86,21 +86,21 @@ public function testListByProjectWithWrongProjectIdentifierThrowsException($proj public function testListByProjectThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects/5/issue_categories.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/IssueCategory/ListNamesByProjectTest.php b/tests/Unit/Api/IssueCategory/ListNamesByProjectTest.php index e10ffc90..9f7f693b 100644 --- a/tests/Unit/Api/IssueCategory/ListNamesByProjectTest.php +++ b/tests/Unit/Api/IssueCategory/ListNamesByProjectTest.php @@ -37,7 +37,7 @@ public function testListNamesByProjectReturnsCorrectResponse($projectIdentifier, ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNamesByProject($projectIdentifier)); @@ -104,7 +104,7 @@ public function testListNamesByProjectCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'IssueCategory 1'], $api->listNamesByProject(5)); @@ -118,7 +118,7 @@ public function testListNamesByProjectCallsHttpClientOnlyOnce(): void #[DataProviderExternal(TestDataProvider::class, 'getInvalidProjectIdentifiers')] public function testListNamesByProjectWithWrongProjectIdentifierThrowsException($projectIdentifier): void { - $api = new IssueCategory($this->createStub(HttpClient::class)); + $api = IssueCategory::fromHttpClient($this->createStub(HttpClient::class)); $this->expectException(InvalidParameterException::class); $this->expectExceptionMessage('Redmine\Api\IssueCategory::listNamesByProject(): Argument #1 ($projectIdentifier) must be of type int or string'); diff --git a/tests/Unit/Api/IssueCategory/RemoveTest.php b/tests/Unit/Api/IssueCategory/RemoveTest.php index 42bfad30..d577e69b 100644 --- a/tests/Unit/Api/IssueCategory/RemoveTest.php +++ b/tests/Unit/Api/IssueCategory/RemoveTest.php @@ -31,7 +31,7 @@ public function testRemoveReturnsCorrectResponse(int $issueId, array $params, st ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->remove($issueId, $params)); diff --git a/tests/Unit/Api/IssueCategory/ShowTest.php b/tests/Unit/Api/IssueCategory/ShowTest.php index 2bef557c..1514591c 100644 --- a/tests/Unit/Api/IssueCategory/ShowTest.php +++ b/tests/Unit/Api/IssueCategory/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($id, string $expectedPath, string ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($id)); diff --git a/tests/Unit/Api/IssueCategory/UpdateTest.php b/tests/Unit/Api/IssueCategory/UpdateTest.php index fc8f0d95..f8f677b8 100644 --- a/tests/Unit/Api/IssueCategory/UpdateTest.php +++ b/tests/Unit/Api/IssueCategory/UpdateTest.php @@ -33,7 +33,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $parameters, str ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update($id, $parameters)); diff --git a/tests/Unit/Api/IssueCategoryTest.php b/tests/Unit/Api/IssueCategoryTest.php index a37e9c50..bbf8a195 100644 --- a/tests/Unit/Api/IssueCategoryTest.php +++ b/tests/Unit/Api/IssueCategoryTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\IssueCategory; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(IssueCategory::class)] class IssueCategoryTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\IssueCategory` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends IssueCategory {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\IssueCategory::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\IssueCategory::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new IssueCategory($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new IssueCategory(MockClient::create()); + $api = IssueCategory::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -50,21 +88,21 @@ public function testAllReturnsClientGetResponseWithProject(string $response, str // Test values $projectId = 5; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects/5/issue_categories.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all($projectId)); @@ -90,26 +128,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/projects/5/issue_categories.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($projectId, $parameters)); @@ -127,23 +160,21 @@ public function testListingReturnsNameIdArray(): void 'IssueCategory 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects/5/issue_categories'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(5)); @@ -161,23 +192,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'IssueCategory 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects/5/issue_categories'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(5)); @@ -196,23 +225,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'IssueCategory 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects/5/issue_categories'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(5, true)); @@ -224,15 +260,22 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"issue_categories":[{"id":1,"name":"IssueCategory 1"},{"id":5,"name":"IssueCategory 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"issue_categories":[{"id":1,"name":"IssueCategory 1"},{"id":5,"name":"IssueCategory 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -259,23 +302,21 @@ public function testGetIdByNameMakesGetRequest(): void // Test values $response = '{"issue_categories":[{"id":5,"name":"IssueCategory 5"}]}'; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects/5/issue_categories.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueCategory($client); + $api = IssueCategory::fromHttpClient($client); // Perform the tests $this->assertFalse($api->getIdByName(5, 'IssueCategory 1')); @@ -284,15 +325,22 @@ public function testGetIdByNameMakesGetRequest(): void public function testGetIdByNameTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"issue_categories":[{"id":1,"name":"IssueCategory 1"},{"id":5,"name":"IssueCategory 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); - - $api = new IssueCategory($client); + $response = '{"issue_categories":[{"id":1,"name":"IssueCategory 1"},{"id":5,"name":"IssueCategory 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = IssueCategory::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( diff --git a/tests/Unit/Api/IssuePriority/FromHttpClientTest.php b/tests/Unit/Api/IssuePriority/FromHttpClientTest.php new file mode 100644 index 00000000..4748ecaa --- /dev/null +++ b/tests/Unit/Api/IssuePriority/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = IssuePriority::fromHttpClient($httpClient); + + $this->assertInstanceOf(IssuePriority::class, $api); + } +} diff --git a/tests/Unit/Api/IssuePriority/ListTest.php b/tests/Unit/Api/IssuePriority/ListTest.php index 81171687..32645172 100644 --- a/tests/Unit/Api/IssuePriority/ListTest.php +++ b/tests/Unit/Api/IssuePriority/ListTest.php @@ -7,6 +7,7 @@ use Redmine\Api\IssuePriority; use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(IssuePriority::class)] class ListTest extends TestCase @@ -17,21 +18,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/enumerations/issue_priorities.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/enumerations/issue_priorities.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssuePriority($client); + $api = IssuePriority::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +45,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/enumerations/issue_priorities.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/enumerations/issue_priorities.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssuePriority($client); + $api = IssuePriority::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($allParameters)); @@ -66,21 +67,21 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/enumerations/issue_priorities.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/enumerations/issue_priorities.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new IssuePriority($client); + $api = IssuePriority::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/IssuePriorityTest.php b/tests/Unit/Api/IssuePriorityTest.php index 7363db9a..8cc73164 100644 --- a/tests/Unit/Api/IssuePriorityTest.php +++ b/tests/Unit/Api/IssuePriorityTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\IssuePriority; use Redmine\Client\Client; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\MockClient; /** @@ -15,12 +17,50 @@ #[CoversClass(IssuePriority::class)] class IssuePriorityTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\IssuePriority` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends IssuePriority {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\IssuePriority::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\IssuePriority::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new IssuePriority($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new IssuePriority(MockClient::create()); + $api = IssuePriority::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +87,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/enumerations/issue_priorities.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/enumerations/issue_priorities.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new IssuePriority($client); + $api = IssuePriority::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,23 +126,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringContains('not-used'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/enumerations/issue_priorities.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssuePriority($client); + $api = IssuePriority::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($allParameters)); diff --git a/tests/Unit/Api/IssueRelation/CreateTest.php b/tests/Unit/Api/IssueRelation/CreateTest.php index 8b486e08..d6dde0ee 100644 --- a/tests/Unit/Api/IssueRelation/CreateTest.php +++ b/tests/Unit/Api/IssueRelation/CreateTest.php @@ -34,7 +34,7 @@ public function testCreateReturnsCorrectResponse(int $issueId, array $parameters ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $return = $api->create($issueId, $parameters); @@ -74,7 +74,7 @@ public function testCreateThrowsExceptionIfResponseContainsEmptyString(): void ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); $this->expectException(SerializerException::class); $this->expectExceptionMessage('Catched error "Syntax error" while decoding JSON: '); @@ -89,7 +89,7 @@ public function testCreateThrowsExceptionWithEmptyParameters(): void $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `issue_to_id`'); @@ -108,7 +108,7 @@ public function testCreateThrowsExceptionIfMandatoyParametersAreMissing(array $p $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `issue_to_id`'); diff --git a/tests/Unit/Api/IssueRelation/FromHttpClientTest.php b/tests/Unit/Api/IssueRelation/FromHttpClientTest.php new file mode 100644 index 00000000..a8d0c9d1 --- /dev/null +++ b/tests/Unit/Api/IssueRelation/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = IssueRelation::fromHttpClient($httpClient); + + $this->assertInstanceOf(IssueRelation::class, $api); + } +} diff --git a/tests/Unit/Api/IssueRelation/ListByIssueIdTest.php b/tests/Unit/Api/IssueRelation/ListByIssueIdTest.php index ea06fa3a..c849159d 100644 --- a/tests/Unit/Api/IssueRelation/ListByIssueIdTest.php +++ b/tests/Unit/Api/IssueRelation/ListByIssueIdTest.php @@ -7,6 +7,7 @@ use Redmine\Api\IssueRelation; use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(IssueRelation::class)] class ListByIssueIdTest extends TestCase @@ -17,21 +18,21 @@ public function testListByIssueIdWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issues/5/relations.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues/5/relations.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByIssueId(5)); @@ -44,21 +45,21 @@ public function testListByIssueIdWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issues/5/relations.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues/5/relations.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByIssueId(5, $parameters)); @@ -66,21 +67,21 @@ public function testListByIssueIdWithParametersReturnsResponse(): void public function testListByIssueIdThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issues/5/relations.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues/5/relations.json', + 'application/json', + '', + 200, + 'application/json', + '', + ], + ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/IssueRelation/RemoveTest.php b/tests/Unit/Api/IssueRelation/RemoveTest.php index 0068abfa..513c2a4b 100644 --- a/tests/Unit/Api/IssueRelation/RemoveTest.php +++ b/tests/Unit/Api/IssueRelation/RemoveTest.php @@ -31,7 +31,7 @@ public function testRemoveReturnsCorrectResponse(int $issueId, string $expectedP ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->remove($issueId)); diff --git a/tests/Unit/Api/IssueRelation/ShowTest.php b/tests/Unit/Api/IssueRelation/ShowTest.php index b28b2091..5bd6de93 100644 --- a/tests/Unit/Api/IssueRelation/ShowTest.php +++ b/tests/Unit/Api/IssueRelation/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($id, string $expectedPath, string ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($id)); diff --git a/tests/Unit/Api/IssueRelationTest.php b/tests/Unit/Api/IssueRelationTest.php index f744ce29..0bc7313e 100644 --- a/tests/Unit/Api/IssueRelationTest.php +++ b/tests/Unit/Api/IssueRelationTest.php @@ -7,6 +7,8 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\IssueRelation; use Redmine\Client\Client; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\MockClient; /** @@ -15,12 +17,50 @@ #[CoversClass(IssueRelation::class)] class IssueRelationTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\IssueRelation` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends IssueRelation {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\IssueRelation::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\IssueRelation::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new IssueRelation($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new IssueRelation(MockClient::create()); + $api = IssueRelation::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +87,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponseWithProject(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issues/5/relations.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues/5/relations.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all(5)); @@ -86,26 +126,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/issues/5/relations.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues/5/relations.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueRelation($client); + $api = IssueRelation::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all(5, $parameters)); diff --git a/tests/Unit/Api/IssueStatus/FromHttpClientTest.php b/tests/Unit/Api/IssueStatus/FromHttpClientTest.php new file mode 100644 index 00000000..bf639ef7 --- /dev/null +++ b/tests/Unit/Api/IssueStatus/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = IssueStatus::fromHttpClient($httpClient); + + $this->assertInstanceOf(IssueStatus::class, $api); + } +} diff --git a/tests/Unit/Api/IssueStatus/ListNamesTest.php b/tests/Unit/Api/IssueStatus/ListNamesTest.php index 98789e13..771808cd 100644 --- a/tests/Unit/Api/IssueStatus/ListNamesTest.php +++ b/tests/Unit/Api/IssueStatus/ListNamesTest.php @@ -33,7 +33,7 @@ public function testListNamesReturnsCorrectResponse(string $expectedPath, int $r ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNames()); @@ -98,7 +98,7 @@ public function testListNamesCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'IssueStatus 1'], $api->listNames()); diff --git a/tests/Unit/Api/IssueStatus/ListTest.php b/tests/Unit/Api/IssueStatus/ListTest.php index 03b1d75e..fe174634 100644 --- a/tests/Unit/Api/IssueStatus/ListTest.php +++ b/tests/Unit/Api/IssueStatus/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\IssueStatus; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(IssueStatus::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issue_statuses.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/issue_statuses.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,23 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issue_statuses.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/IssueStatusTest.php b/tests/Unit/Api/IssueStatusTest.php index 4bd64210..2e7eb5fa 100644 --- a/tests/Unit/Api/IssueStatusTest.php +++ b/tests/Unit/Api/IssueStatusTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\IssueStatus; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(IssueStatus::class)] class IssueStatusTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\IssueStatus` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends IssueStatus {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\IssueStatus::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\IssueStatus::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new IssueStatus($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new IssueStatus(MockClient::create()); + $api = IssueStatus::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issue_statuses.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,26 +124,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/issue_statuses.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); @@ -123,23 +156,21 @@ public function testListingReturnsNameIdArray(): void 'IssueStatus 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/issue_statuses.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -157,23 +188,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'IssueStatus 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/issue_statuses.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -192,23 +221,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'IssueStatus 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/issue_statuses.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(true)); @@ -220,15 +256,22 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"issue_statuses":[{"id":1,"name":"IssueStatus 1"},{"id":5,"name":"IssueStatus 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"issue_statuses":[{"id":1,"name":"IssueStatus 1"},{"id":5,"name":"IssueStatus 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -255,23 +298,21 @@ public function testGetIdByNameMakesGetRequest(): void // Test values $response = '{"issue_statuses":[{"id":5,"name":"IssueStatus 5"}]}'; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/issue_statuses.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new IssueStatus($client); + $api = IssueStatus::fromHttpClient($client); // Perform the tests $this->assertFalse($api->getIdByName('IssueStatus 1')); @@ -280,15 +321,22 @@ public function testGetIdByNameMakesGetRequest(): void public function testGetIdByNameTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"issue_statuses":[{"id":1,"name":"IssueStatus 1"},{"id":5,"name":"IssueStatus 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); - - $api = new IssueStatus($client); + $response = '{"issue_statuses":[{"id":1,"name":"IssueStatus 1"},{"id":5,"name":"IssueStatus 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = IssueStatus::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( diff --git a/tests/Unit/Api/IssueTest.php b/tests/Unit/Api/IssueTest.php index cfddb4bc..3fef1310 100644 --- a/tests/Unit/Api/IssueTest.php +++ b/tests/Unit/Api/IssueTest.php @@ -13,9 +13,7 @@ use Redmine\Api\User; use Redmine\Client\Client; use Redmine\Http\HttpClient; -use Redmine\Http\Response; use Redmine\Tests\Fixtures\AssertingHttpClient; -use Redmine\Tests\Fixtures\MockClient; /** * @author Malte Gerth @@ -23,6 +21,44 @@ #[CoversClass(Issue::class)] class IssueTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Issue` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Issue {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Issue::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Issue::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Issue($this->createStub(HttpClient::class)); + } + public static function getPriorityConstantsData(): array { return [ @@ -50,7 +86,7 @@ public function testPriorityConstants(int $expected, int $value): void */ public function testAllTriggersDeprecationWarning(): void { - $api = new Issue(MockClient::create()); + $api = Issue::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -77,21 +113,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/issues.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -116,26 +152,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/issues.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issues.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); @@ -157,8 +188,7 @@ public function testCreateWithClientCleansParameters(): void 'author' => 'user_4', ]; - // Create the used mock objects - $httpClient = AssertingHttpClient::create( + $client = AssertingHttpClient::create( $this, [ 'GET', @@ -207,21 +237,21 @@ public function testCreateWithClientCleansParameters(): void ], ); - $client = $this->createMock(Client::class); - $client->expects($this->exactly(5)) + $legacyClient = $this->createMock(Client::class); + $legacyClient->expects($this->exactly(5)) ->method('getApi') ->willReturnMap( [ - ['project', new Project($httpClient)], - ['issue_category', new IssueCategory($httpClient)], - ['issue_status', new IssueStatus($httpClient)], - ['tracker', new Tracker($httpClient)], - ['user', new User($httpClient)], + ['project', Project::fromHttpClient($client)], + ['issue_category', IssueCategory::fromHttpClient($client)], + ['issue_status', IssueStatus::fromHttpClient($client)], + ['tracker', new Tracker($client)], + ['user', new User($client)], ], ) ; - $client->expects($this->once()) + $legacyClient->expects($this->once()) ->method('requestPost') ->with( '/issues.xml', @@ -232,15 +262,15 @@ public function testCreateWithClientCleansParameters(): void XML, ) ->willReturn(true); - $client->expects($this->exactly(1)) + $legacyClient->expects($this->exactly(1)) ->method('getLastResponseBody') ->willReturn($response); - $client->expects($this->exactly(1)) + $legacyClient->expects($this->exactly(1)) ->method('getLastResponseContentType') ->willReturn('application/xml'); // Create the object under test - $api = new Issue($client); + $api = new Issue($legacyClient); // Perform the tests $this->assertXmlStringEqualsXmlString($response, $api->create($parameters)->asXML()); diff --git a/tests/Unit/Api/Membership/CreateTest.php b/tests/Unit/Api/Membership/CreateTest.php index 2cbfd387..4c88f763 100644 --- a/tests/Unit/Api/Membership/CreateTest.php +++ b/tests/Unit/Api/Membership/CreateTest.php @@ -34,7 +34,7 @@ public function testCreateReturnsCorrectResponse(int $identifier, array $paramet ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $return = $api->create($identifier, $parameters); @@ -81,7 +81,7 @@ public function testCreateReturnsEmptyString(): void ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $return = $api->create(5, ['user_id' => 4, 'role_ids' => 2]); @@ -91,11 +91,10 @@ public function testCreateReturnsEmptyString(): void public function testCreateThrowsExceptionWithEmptyParameters(): void { - // Create the used mock objects $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `user_id`, `role_ids`'); @@ -110,11 +109,10 @@ public function testCreateThrowsExceptionWithEmptyParameters(): void #[DataProvider('incompleteCreateParameterProvider')] public function testCreateThrowsExceptionIfMandatoyParametersAreMissing(array $parameters): void { - // Create the used mock objects $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `user_id`, `role_ids`'); diff --git a/tests/Unit/Api/Membership/FromHttpClientTest.php b/tests/Unit/Api/Membership/FromHttpClientTest.php new file mode 100644 index 00000000..98998f14 --- /dev/null +++ b/tests/Unit/Api/Membership/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Membership::fromHttpClient($httpClient); + + $this->assertInstanceOf(Membership::class, $api); + } +} diff --git a/tests/Unit/Api/Membership/ListByProjectTest.php b/tests/Unit/Api/Membership/ListByProjectTest.php index b1bac7be..95a808c4 100644 --- a/tests/Unit/Api/Membership/ListByProjectTest.php +++ b/tests/Unit/Api/Membership/ListByProjectTest.php @@ -6,10 +6,10 @@ use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; use Redmine\Api\Membership; -use Redmine\Client\Client; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\UnexpectedResponseException; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\TestDataProvider; #[CoversClass(Membership::class)] @@ -21,21 +21,21 @@ public function testListByProjectWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/5/memberships.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/memberships.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject(5)); @@ -48,21 +48,21 @@ public function testListByProjectWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/project-slug/memberships.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/project-slug/memberships.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject('project-slug', $parameters)); @@ -74,7 +74,7 @@ public function testListByProjectWithParametersReturnsResponse(): void #[DataProviderExternal(TestDataProvider::class, 'getInvalidProjectIdentifiers')] public function testListByProjectWithWrongProjectIdentifierThrowsException($projectIdentifier): void { - $api = new Membership(MockClient::create()); + $api = Membership::fromHttpClient($this->createStub(HttpClient::class)); $this->expectException(InvalidParameterException::class); $this->expectExceptionMessage('Redmine\Api\Membership::listByProject(): Argument #1 ($projectIdentifier) must be of type int or string'); @@ -84,21 +84,23 @@ public function testListByProjectWithWrongProjectIdentifierThrowsException($proj public function testListByProjectThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects/5/memberships.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/memberships.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Membership/RemoveMemberTest.php b/tests/Unit/Api/Membership/RemoveMemberTest.php index 495ea5d3..8c2fd51a 100644 --- a/tests/Unit/Api/Membership/RemoveMemberTest.php +++ b/tests/Unit/Api/Membership/RemoveMemberTest.php @@ -40,7 +40,7 @@ public function testRemoveMemberReturnsCorrectResponse(int $projectIdentifier, i ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->removeMember($projectIdentifier, $userId, $params)); @@ -84,7 +84,7 @@ public function testRemoveMemberReturnsFalseIfUserIsNotMemberOfProject(): void ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertFalse($api->removeMember(1, 2)); @@ -106,7 +106,7 @@ public function testRemoveMemberReturnsFalseIfMemberlistIsMissing(): void ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertFalse($api->removeMember(1, 2)); diff --git a/tests/Unit/Api/Membership/RemoveTest.php b/tests/Unit/Api/Membership/RemoveTest.php index b18f65b0..4e668632 100644 --- a/tests/Unit/Api/Membership/RemoveTest.php +++ b/tests/Unit/Api/Membership/RemoveTest.php @@ -31,7 +31,7 @@ public function testRemoveReturnsCorrectResponse(int $id, string $expectedPath, ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->remove($id)); diff --git a/tests/Unit/Api/Membership/UpdateTest.php b/tests/Unit/Api/Membership/UpdateTest.php index f6b70d20..df0a80bf 100644 --- a/tests/Unit/Api/Membership/UpdateTest.php +++ b/tests/Unit/Api/Membership/UpdateTest.php @@ -33,7 +33,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $parameters, str ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update($id, $parameters)); @@ -78,7 +78,7 @@ public function testUpdateReturnsEmptyString(): void ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $return = $api->update(5, ['user_id' => 4, 'role_ids' => 2]); @@ -92,7 +92,7 @@ public function testUpdateThrowsExceptionWithEmptyParameters(): void $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `role_ids`'); @@ -111,7 +111,7 @@ public function testUpdateThrowsExceptionIfMandatoyParametersAreMissing(array $p $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `role_ids`'); diff --git a/tests/Unit/Api/MembershipTest.php b/tests/Unit/Api/MembershipTest.php index 70af3a16..3bb0c5ce 100644 --- a/tests/Unit/Api/MembershipTest.php +++ b/tests/Unit/Api/MembershipTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Membership; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(Membership::class)] class MembershipTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Membership` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Membership {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Membership::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Membership::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Membership($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new Membership(MockClient::create()); + $api = Membership::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponseWithProject(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects/5/memberships.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/memberships.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all(5)); @@ -86,26 +124,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/projects/5/memberships.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/memberships.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Membership($client); + $api = Membership::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all(5, $parameters)); diff --git a/tests/Unit/Api/News/FromHttpClientTest.php b/tests/Unit/Api/News/FromHttpClientTest.php new file mode 100644 index 00000000..7785d5d3 --- /dev/null +++ b/tests/Unit/Api/News/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = News::fromHttpClient($httpClient); + + $this->assertInstanceOf(News::class, $api); + } +} diff --git a/tests/Unit/Api/News/ListByProjectTest.php b/tests/Unit/Api/News/ListByProjectTest.php index 4a234d4c..ab9c4a72 100644 --- a/tests/Unit/Api/News/ListByProjectTest.php +++ b/tests/Unit/Api/News/ListByProjectTest.php @@ -6,10 +6,10 @@ use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; use Redmine\Api\News; -use Redmine\Client\Client; use Redmine\Exception\InvalidParameterException; use Redmine\Exception\UnexpectedResponseException; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\TestDataProvider; #[CoversClass(News::class)] @@ -22,21 +22,21 @@ public function testListByProjectWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/5/news.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/news.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject($projectId)); @@ -50,21 +50,21 @@ public function testListByProjectWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects/5/news.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/news.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByProject($projectId, $parameters)); @@ -76,7 +76,7 @@ public function testListByProjectWithParametersReturnsResponse(): void #[DataProviderExternal(TestDataProvider::class, 'getInvalidProjectIdentifiers')] public function testListByProjectWithWrongProjectIdentifierThrowsException($projectIdentifier): void { - $api = new News(MockClient::create()); + $api = News::fromHttpClient($this->createStub(HttpClient::class)); $this->expectException(InvalidParameterException::class); $this->expectExceptionMessage('Redmine\Api\News::listByProject(): Argument #1 ($projectIdentifier) must be of type int or string'); @@ -86,21 +86,23 @@ public function testListByProjectWithWrongProjectIdentifierThrowsException($proj public function testListByProjectThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects/5/news.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/news.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/News/ListTest.php b/tests/Unit/Api/News/ListTest.php index d8116065..0732cd2a 100644 --- a/tests/Unit/Api/News/ListTest.php +++ b/tests/Unit/Api/News/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\News; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(News::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/news.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/news.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/news.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/news.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,23 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/news.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/news.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/NewsTest.php b/tests/Unit/Api/NewsTest.php index 32b589f4..5daed4ee 100644 --- a/tests/Unit/Api/NewsTest.php +++ b/tests/Unit/Api/NewsTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\News; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(News::class)] class NewsTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\News` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends News {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\News::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\News::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new News($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new News(MockClient::create()); + $api = News::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/news.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/news.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,23 +124,21 @@ public function testAllReturnsClientGetResponseWithProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects/5/news.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/news.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($projectId)); @@ -119,23 +155,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringContains('not-used'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/news.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new News($client); + $api = News::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($projectId, $parameters)); diff --git a/tests/Unit/Api/Project/ArchiveTest.php b/tests/Unit/Api/Project/ArchiveTest.php index 6a9216da..8ca6374e 100644 --- a/tests/Unit/Api/Project/ArchiveTest.php +++ b/tests/Unit/Api/Project/ArchiveTest.php @@ -28,7 +28,7 @@ public function testArchiveReturnsTrue(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->assertTrue($api->archive(5)); } @@ -46,7 +46,7 @@ public function testArchiveThrowsUnexpectedResponseException(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); @@ -58,7 +58,7 @@ public function testArchiveWithoutIntOrStringThrowsInvalidArgumentException(): v { $client = $this->createStub(HttpClient::class); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Redmine\Api\Project::archive(): Argument #1 ($projectIdentifier) must be of type int or string'); diff --git a/tests/Unit/Api/Project/CloseTest.php b/tests/Unit/Api/Project/CloseTest.php index 470f517f..f36901ae 100644 --- a/tests/Unit/Api/Project/CloseTest.php +++ b/tests/Unit/Api/Project/CloseTest.php @@ -26,7 +26,7 @@ public function testCloseReturnsTrue(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->assertTrue($api->close(5)); } @@ -44,7 +44,7 @@ public function testCloseThrowsUnexpectedResponseException(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); @@ -56,7 +56,7 @@ public function testCloseWithoutIntOrStringThrowsInvalidArgumentException(): voi { $client = $this->createStub(HttpClient::class); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Redmine\Api\Project::close(): Argument #1 ($projectIdentifier) must be of type int or string'); diff --git a/tests/Unit/Api/Project/CreateTest.php b/tests/Unit/Api/Project/CreateTest.php index 87217ceb..13665670 100644 --- a/tests/Unit/Api/Project/CreateTest.php +++ b/tests/Unit/Api/Project/CreateTest.php @@ -34,7 +34,7 @@ public function testCreateReturnsCorrectResponse(array $parameters, string $expe ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $return = $api->create($parameters); @@ -147,7 +147,7 @@ public function testCreateReturnsEmptyString(): void ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $return = $api->create(['identifier' => 'test-project', 'name' => 'Test Project']); @@ -161,7 +161,7 @@ public function testCreateThrowsExceptionWithEmptyParameters(): void $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `name`, `identifier`'); @@ -180,7 +180,7 @@ public function testCreateThrowsExceptionIfMandatoyParametersAreMissing(array $p $client = $this->createStub(HttpClient::class); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(MissingParameterException::class); $this->expectExceptionMessage('Theses parameters are mandatory: `name`, `identifier`'); diff --git a/tests/Unit/Api/Project/FromHttpClientTest.php b/tests/Unit/Api/Project/FromHttpClientTest.php new file mode 100644 index 00000000..ecb25135 --- /dev/null +++ b/tests/Unit/Api/Project/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Project::fromHttpClient($httpClient); + + $this->assertInstanceOf(Project::class, $api); + } +} diff --git a/tests/Unit/Api/Project/ListNamesTest.php b/tests/Unit/Api/Project/ListNamesTest.php index 44136945..8f2553e1 100644 --- a/tests/Unit/Api/Project/ListNamesTest.php +++ b/tests/Unit/Api/Project/ListNamesTest.php @@ -33,7 +33,7 @@ public function testListNamesReturnsCorrectResponse(string $expectedPath, int $r ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNames()); @@ -126,7 +126,7 @@ public function testListNamesWithALotOfProjectsHandlesPagination(): void ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($assertData, $api->listNames()); @@ -157,7 +157,7 @@ public function testListNamesCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'Project 1'], $api->listNames()); diff --git a/tests/Unit/Api/Project/ListTest.php b/tests/Unit/Api/Project/ListTest.php index ff6c7b52..2d7640aa 100644 --- a/tests/Unit/Api/Project/ListTest.php +++ b/tests/Unit/Api/Project/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\Project; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Project::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/projects.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,23 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Project/RemoveTest.php b/tests/Unit/Api/Project/RemoveTest.php index 0a1c38c5..1ff74cfd 100644 --- a/tests/Unit/Api/Project/RemoveTest.php +++ b/tests/Unit/Api/Project/RemoveTest.php @@ -31,7 +31,7 @@ public function testRemoveReturnsCorrectResponse(int $id, string $expectedPath, ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($response, $api->remove($id)); diff --git a/tests/Unit/Api/Project/ReopenTest.php b/tests/Unit/Api/Project/ReopenTest.php index bb111378..4b4f3f99 100644 --- a/tests/Unit/Api/Project/ReopenTest.php +++ b/tests/Unit/Api/Project/ReopenTest.php @@ -26,7 +26,7 @@ public function testReopenReturnsTrue(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->assertTrue($api->reopen(5)); } @@ -44,7 +44,7 @@ public function testReopenThrowsUnexpectedResponseException(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); @@ -56,7 +56,7 @@ public function testReopenWithoutIntOrStringThrowsInvalidArgumentException(): vo { $client = $this->createStub(HttpClient::class); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Redmine\Api\Project::reopen(): Argument #1 ($projectIdentifier) must be of type int or string'); diff --git a/tests/Unit/Api/Project/ShowTest.php b/tests/Unit/Api/Project/ShowTest.php index 5d36fd1a..3c30edb6 100644 --- a/tests/Unit/Api/Project/ShowTest.php +++ b/tests/Unit/Api/Project/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($identifier, array $params, strin ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($identifier, $params)); diff --git a/tests/Unit/Api/Project/UnarchiveTest.php b/tests/Unit/Api/Project/UnarchiveTest.php index e3c57b00..86f777b5 100644 --- a/tests/Unit/Api/Project/UnarchiveTest.php +++ b/tests/Unit/Api/Project/UnarchiveTest.php @@ -26,7 +26,7 @@ public function testUnarchiveReturnsTrue(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->assertTrue($api->unarchive(5)); } @@ -44,7 +44,7 @@ public function testUnarchiveThrowsUnexpectedResponseException(): void ], ); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); @@ -56,7 +56,7 @@ public function testUnarchiveWithoutIntOrStringThrowsInvalidArgumentException(): { $client = $this->createStub(HttpClient::class); - $api = new Project($client); + $api = Project::fromHttpClient($client); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Redmine\Api\Project::unarchive(): Argument #1 ($projectIdentifier) must be of type int or string'); diff --git a/tests/Unit/Api/Project/UpdateTest.php b/tests/Unit/Api/Project/UpdateTest.php index 5f12e417..52fcc23a 100644 --- a/tests/Unit/Api/Project/UpdateTest.php +++ b/tests/Unit/Api/Project/UpdateTest.php @@ -33,7 +33,7 @@ public function testUpdateReturnsCorrectResponse(int $id, array $parameters, str ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame('', $api->update($id, $parameters)); diff --git a/tests/Unit/Api/ProjectTest.php b/tests/Unit/Api/ProjectTest.php index b88452ca..ec153439 100644 --- a/tests/Unit/Api/ProjectTest.php +++ b/tests/Unit/Api/ProjectTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Project; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; use SimpleXMLElement; @@ -17,12 +17,50 @@ #[CoversClass(Project::class)] class ProjectTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Project` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Project {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Project::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Project::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Project($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new Project(MockClient::create()); + $api = Project::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -49,21 +87,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/projects.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -88,26 +126,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/projects.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); @@ -125,23 +158,21 @@ public function testListingReturnsNameIdArray(): void 'Project 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -159,23 +190,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'Project 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -194,23 +223,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'Project 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(true)); @@ -222,15 +258,22 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"projects":[{"id":1,"name":"Project 1"},{"id":5,"name":"Project 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"projects":[{"id":1,"name":"Project 1"},{"id":5,"name":"Project 5"}]}'; - $api = new Project($client); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = Project::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -257,23 +300,21 @@ public function testGetIdByNameMakesGetRequest(): void // Test values $response = '{"projects":[{"id":5,"name":"Project 5"}]}'; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/projects.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Project($client); + $api = Project::fromHttpClient($client); // Perform the tests $this->assertFalse($api->getIdByName('Project 1')); @@ -282,15 +323,22 @@ public function testGetIdByNameMakesGetRequest(): void public function testGetIdByNameTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"projects":[{"id":1,"name":"Project 1"},{"id":5,"name":"Project 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = '{"projects":[{"id":1,"name":"Project 1"},{"id":5,"name":"Project 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); - $api = new Project($client); + $api = Project::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -311,9 +359,9 @@ function ($errno, $errstr): bool { public function testDeprecatedPrepareParamsXml(): void { - $client = $this->createStub(Client::class); + $client = $this->createStub(HttpClient::class); - $api = new Project($client); + $api = Project::fromHttpClient($client); $method = new ReflectionMethod($api, 'prepareParamsXml'); if (PHP_VERSION_ID < 80100) { diff --git a/tests/Unit/Api/Query/FromHttpClientTest.php b/tests/Unit/Api/Query/FromHttpClientTest.php new file mode 100644 index 00000000..8cc3e8fe --- /dev/null +++ b/tests/Unit/Api/Query/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Query::fromHttpClient($httpClient); + + $this->assertInstanceOf(Query::class, $api); + } +} diff --git a/tests/Unit/Api/Query/ListTest.php b/tests/Unit/Api/Query/ListTest.php index 4062c04e..70373ad7 100644 --- a/tests/Unit/Api/Query/ListTest.php +++ b/tests/Unit/Api/Query/ListTest.php @@ -7,6 +7,7 @@ use Redmine\Api\Query; use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Query::class)] class ListTest extends TestCase @@ -17,21 +18,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/queries.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/queries.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Query($client); + $api = Query::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +45,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->any()) - ->method('requestGet') - ->with('/queries.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/queries.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Query($client); + $api = Query::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +67,23 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/queries.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/queries.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Query($client); + $api = Query::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/QueryTest.php b/tests/Unit/Api/QueryTest.php index 86af788c..93eb7b56 100644 --- a/tests/Unit/Api/QueryTest.php +++ b/tests/Unit/Api/QueryTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Query; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(Query::class)] class QueryTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Query` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Query {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Query::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Query::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Query($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new Query(MockClient::create()); + $api = Query::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/queries.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/queries.json', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Query($client); + $api = Query::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,26 +124,21 @@ public function testAllReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->any()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/queries.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/queries.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Query($client); + $api = Query::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); diff --git a/tests/Unit/Api/Role/FromHttpClientTest.php b/tests/Unit/Api/Role/FromHttpClientTest.php new file mode 100644 index 00000000..fdba6977 --- /dev/null +++ b/tests/Unit/Api/Role/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Role::fromHttpClient($httpClient); + + $this->assertInstanceOf(Role::class, $api); + } +} diff --git a/tests/Unit/Api/Role/ListNamesTest.php b/tests/Unit/Api/Role/ListNamesTest.php index aa849d73..902eedc2 100644 --- a/tests/Unit/Api/Role/ListNamesTest.php +++ b/tests/Unit/Api/Role/ListNamesTest.php @@ -33,7 +33,7 @@ public function testListNamesReturnsCorrectResponse(string $expectedPath, int $r ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->listNames()); @@ -98,7 +98,7 @@ public function testListNamesCallsHttpClientOnlyOnce(): void ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame([1 => 'Role 1'], $api->listNames()); diff --git a/tests/Unit/Api/Role/ListTest.php b/tests/Unit/Api/Role/ListTest.php index 301e8054..bac61933 100644 --- a/tests/Unit/Api/Role/ListTest.php +++ b/tests/Unit/Api/Role/ListTest.php @@ -5,8 +5,8 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Redmine\Api\Role; -use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Role::class)] class ListTest extends TestCase @@ -17,21 +17,21 @@ public function testListWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/roles.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list()); @@ -44,21 +44,21 @@ public function testListWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/roles.json?limit=25&offset=0&0=not-used') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->list($parameters)); @@ -66,21 +66,23 @@ public function testListWithParametersReturnsResponse(): void public function testListThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/roles.json') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Role/ShowTest.php b/tests/Unit/Api/Role/ShowTest.php index 7fba909c..4d12086f 100644 --- a/tests/Unit/Api/Role/ShowTest.php +++ b/tests/Unit/Api/Role/ShowTest.php @@ -31,7 +31,7 @@ public function testShowReturnsCorrectResponse($id, string $expectedPath, string ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->show($id)); diff --git a/tests/Unit/Api/RoleTest.php b/tests/Unit/Api/RoleTest.php index 249dba22..dd4b47c9 100644 --- a/tests/Unit/Api/RoleTest.php +++ b/tests/Unit/Api/RoleTest.php @@ -6,8 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Role; -use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @author Malte Gerth @@ -15,12 +15,50 @@ #[CoversClass(Role::class)] class RoleTest extends TestCase { + public function testExtendingTheClassTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Class `Redmine\Api\Role` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Role {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Role::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Role::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Role($this->createStub(HttpClient::class)); + } + /** * Test all(). */ public function testAllTriggersDeprecationWarning(): void { - $api = new Role(MockClient::create()); + $api = Role::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -47,21 +85,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testAllReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/roles.json') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->all()); @@ -86,26 +124,21 @@ public function testAllReturnsClientGetResponseWithParametersAndProject(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/roles.json'), - $this->stringContains('not-used'), - ), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json?limit=25&offset=0&0=not-used', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->all($parameters)); @@ -123,23 +156,21 @@ public function testListingReturnsNameIdArray(): void 'Role 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/roles.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -157,23 +188,21 @@ public function testListingCallsGetOnlyTheFirstTime(): void 'Role 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with( - $this->stringStartsWith('/roles.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing()); @@ -192,23 +221,30 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void 'Role 5' => 5, ]; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(2)) - ->method('requestGet') - ->with( - $this->stringStartsWith('/roles.json'), - ) - ->willReturn(true); - $client->expects($this->exactly(2)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(2)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Role($client); + $api = Role::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listing(true)); @@ -220,15 +256,22 @@ public function testListingCallsGetEveryTimeWithForceUpdate(): void */ public function testListingTriggersDeprecationWarning(): void { - $client = $this->createStub(Client::class); - $client->method('requestGet') - ->willReturn(true); - $client->method('getLastResponseBody') - ->willReturn('{"roles":[{"id":1,"name":"Role 1"},{"id":5,"name":"Role 5"}]}'); - $client->method('getLastResponseContentType') - ->willReturn('application/json'); - - $api = new Role($client); + $response = '{"roles":[{"id":1,"name":"Role 1"},{"id":5,"name":"Role 5"}]}'; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/roles.json', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); + + $api = Role::fromHttpClient($client); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( diff --git a/tests/Unit/Api/Search/FromHttpClientTest.php b/tests/Unit/Api/Search/FromHttpClientTest.php new file mode 100644 index 00000000..63340a93 --- /dev/null +++ b/tests/Unit/Api/Search/FromHttpClientTest.php @@ -0,0 +1,21 @@ +createStub(HttpClient::class); + + $api = Search::fromHttpClient($httpClient); + + $this->assertInstanceOf(Search::class, $api); + } +} diff --git a/tests/Unit/Api/Search/ListByQueryTest.php b/tests/Unit/Api/Search/ListByQueryTest.php index 02848dcb..319c3101 100644 --- a/tests/Unit/Api/Search/ListByQueryTest.php +++ b/tests/Unit/Api/Search/ListByQueryTest.php @@ -7,6 +7,7 @@ use Redmine\Api\Search; use Redmine\Client\Client; use Redmine\Exception\UnexpectedResponseException; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Search::class)] class ListByQueryTest extends TestCase @@ -17,21 +18,21 @@ public function testListByQueryWithoutParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestGet') - ->with('/search.json?limit=25&offset=0&q=query') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/search.json?limit=25&offset=0&q=query', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Search($client); + $api = Search::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByQuery('query')); @@ -44,21 +45,21 @@ public function testListByQueryWithParametersReturnsResponse(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->any()) - ->method('requestGet') - ->with('/search.json?limit=25&offset=0&0=not-used&q=query') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/search.json?limit=25&offset=0&0=not-used&q=query', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Search($client); + $api = Search::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->listByQuery('query', $parameters)); @@ -66,21 +67,23 @@ public function testListByQueryWithParametersReturnsResponse(): void public function testListByQueryThrowsException(): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/search.json?limit=25&offset=0&q=query') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn(''); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $response = ''; + + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/search.json?limit=25&offset=0&q=query', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Search($client); + $api = Search::fromHttpClient($client); $this->expectException(UnexpectedResponseException::class); $this->expectExceptionMessage('The Redmine server replied with an unexpected response.'); diff --git a/tests/Unit/Api/Search/SearchTest.php b/tests/Unit/Api/Search/SearchTest.php index 5756a619..56255621 100644 --- a/tests/Unit/Api/Search/SearchTest.php +++ b/tests/Unit/Api/Search/SearchTest.php @@ -7,14 +7,15 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\Search; use Redmine\Client\Client; -use Redmine\Tests\Fixtures\MockClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\Fixtures\AssertingHttpClient; #[CoversClass(Search::class)] class SearchTest extends TestCase { public function testSearchTriggersDeprecationWarning(): void { - $api = new Search(MockClient::create()); + $api = Search::fromHttpClient($this->createStub(HttpClient::class)); // PHPUnit 10 compatible way to test trigger_error(). set_error_handler( @@ -39,21 +40,21 @@ function ($errno, $errstr): bool { #[DataProvider('getAllData')] public function testSearchReturnsClientGetResponse(string $response, string $responseType, $expectedResponse): void { - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->exactly(1)) - ->method('requestGet') - ->with('/search.json?limit=25&offset=0&q=query') - ->willReturn(true); - $client->expects($this->atLeast(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn($responseType); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/search.json?limit=25&offset=0&q=query', + 'application/json', + '', + 200, + $responseType, + $response, + ], + ); // Create the object under test - $api = new Search($client); + $api = Search::fromHttpClient($client); // Perform the tests $this->assertSame($expectedResponse, $api->search('query')); @@ -75,21 +76,21 @@ public function testSearchReturnsClientGetResponseWithParameters(): void $response = '["API Response"]'; $expectedReturn = ['API Response']; - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->any()) - ->method('requestGet') - ->with('/search.json?limit=25&offset=0&0=not-used&q=query') - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/json'); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/search.json?limit=25&offset=0&0=not-used&q=query', + 'application/json', + '', + 200, + 'application/json', + $response, + ], + ); // Create the object under test - $api = new Search($client); + $api = Search::fromHttpClient($client); // Perform the tests $this->assertSame($expectedReturn, $api->search('query', $parameters)); diff --git a/tests/Unit/Api/SearchTest.php b/tests/Unit/Api/SearchTest.php new file mode 100644 index 00000000..498de836 --- /dev/null +++ b/tests/Unit/Api/SearchTest.php @@ -0,0 +1,50 @@ +assertSame( + 'Class `Redmine\Api\Search` will declared as final in v3.0.0, stop extending it.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new class ($this->createStub(HttpClient::class)) extends Search {}; + } + + public function testConstructorTriggersDeprecationWarning(): void + { + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + 'Method `Redmine\Api\Search::__construct()` is deprecated since v2.9.0 and will declared as private in v3.0.0, use `Redmine\Api\Search::fromHttpClient()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + new Search($this->createStub(HttpClient::class)); + } +} diff --git a/tests/Unit/Client/NativeCurlClientTest.php b/tests/Unit/Client/NativeCurlClientTest.php index bd0c03f5..e9b24471 100644 --- a/tests/Unit/Client/NativeCurlClientTest.php +++ b/tests/Unit/Client/NativeCurlClientTest.php @@ -966,7 +966,7 @@ public function testCurlErrorThrowsException(): void * @dataProvider getApiClassesProvider */ #[DataProvider('getApiClassesProvider')] - public function testGetApiShouldReturnApiInstance(string $apiName, string $class): void + public function testGetApiReturnsApiInstance(string $apiName, string $class): void { $client = new NativeCurlClient( 'http://test.local', @@ -1001,7 +1001,24 @@ public static function getApiClassesProvider(): array ]; } - public function testGetApiShouldThrowException(): void + /** + * @dataProvider getApiClassesProvider + */ + #[DataProvider('getApiClassesProvider')] + public function testGetApiOnMultipleCallsReturnSameApiInstance(string $apiName, string $class): void + { + $client = new NativeCurlClient( + 'http://test.local', + 'access_token', + ); + + $this->assertSame( + $client->getApi($apiName), + $client->getApi($apiName), + ); + } + + public function testGetApiWithInvalidApiThrowsException(): void { $client = new NativeCurlClient( 'http://test.local',