diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bea9d6d..d5a34eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ 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. - Add support for PHP 8.5 - Add support for Redmine 6.1. @@ -16,6 +21,19 @@ 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. + ### Removed - Drop support for Redmine 5.0.x. 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..93e5892d 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; 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/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 c929b4e6..2e5efc0c 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 870c833a..3711cd1a 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 5d206f62..8417c61e 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 59ce6fba..5a53c6ce 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/IssueTest.php b/tests/Unit/Api/IssueTest.php index cfddb4bc..5ebd0207 100644 --- a/tests/Unit/Api/IssueTest.php +++ b/tests/Unit/Api/IssueTest.php @@ -6,16 +6,8 @@ use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Redmine\Api\Issue; -use Redmine\Api\IssueCategory; -use Redmine\Api\IssueStatus; -use Redmine\Api\Project; -use Redmine\Api\Tracker; -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 +15,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 +80,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 +107,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 +146,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 +182,7 @@ public function testCreateWithClientCleansParameters(): void 'author' => 'user_4', ]; - // Create the used mock objects - $httpClient = AssertingHttpClient::create( + $client = AssertingHttpClient::create( $this, [ 'GET', @@ -205,42 +229,23 @@ public function testCreateWithClientCleansParameters(): void 'application/json', '{"users":[{"id":3,"login":"user_3"},{"id":4,"login":"user_4"}]}', ], - ); - - $client = $this->createMock(Client::class); - $client->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)], - ], - ) - ; - - $client->expects($this->once()) - ->method('requestPost') - ->with( + [ + 'POST', '/issues.xml', + 'application/xml', <<< XML 156234 XML, - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - $client->expects($this->exactly(1)) - ->method('getLastResponseContentType') - ->willReturn('application/xml'); + 200, + 'application/xml', + $response, + ], + ); // Create the object under test - $api = new Issue($client); + $api = Issue::fromHttpClient($client); // Perform the tests $this->assertXmlStringEqualsXmlString($response, $api->create($parameters)->asXML());