diff --git a/src/Redmine/Api/AbstractApi.php b/src/Redmine/Api/AbstractApi.php index 3b2851c2..f7001a27 100644 --- a/src/Redmine/Api/AbstractApi.php +++ b/src/Redmine/Api/AbstractApi.php @@ -9,6 +9,7 @@ use Redmine\Exception; use Redmine\Exception\SerializerException; use Redmine\Http\HttpClient; +use Redmine\Http\Request; use Redmine\Http\Response; use Redmine\Serializer\JsonSerializer; use Redmine\Serializer\PathSerializer; @@ -108,9 +109,13 @@ public function lastCallFailed() */ protected function get($path, $decodeIfJson = true) { - $this->lastResponse = $this->getHttpClient()->request('GET', strval($path)); + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( + 'GET', + strval($path), + $this->getContentTypeFromPath(strval($path)) + )); - $body = $this->lastResponse->getBody(); + $body = $this->lastResponse->getContent(); $contentType = $this->lastResponse->getContentType(); // if response is XML, return a SimpleXMLElement object @@ -139,9 +144,14 @@ protected function get($path, $decodeIfJson = true) */ protected function post($path, $data) { - $this->lastResponse = $this->getHttpClient()->request('POST', strval($path), $data); - - $body = $this->lastResponse->getBody(); + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( + 'POST', + strval($path), + $this->getContentTypeFromPath(strval($path)), + $data + )); + + $body = $this->lastResponse->getContent(); $contentType = $this->lastResponse->getContentType(); // if response is XML, return a SimpleXMLElement object @@ -162,9 +172,14 @@ protected function post($path, $data) */ protected function put($path, $data) { - $this->lastResponse = $this->getHttpClient()->request('PUT', strval($path), $data); - - $body = $this->lastResponse->getBody(); + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( + 'PUT', + strval($path), + $this->getContentTypeFromPath(strval($path)), + $data + )); + + $body = $this->lastResponse->getContent(); $contentType = $this->lastResponse->getContentType(); // if response is XML, return a SimpleXMLElement object @@ -184,9 +199,13 @@ protected function put($path, $data) */ protected function delete($path) { - $this->lastResponse = $this->getHttpClient()->request('DELETE', strval($path)); + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( + 'DELETE', + strval($path), + $this->getContentTypeFromPath(strval($path)) + )); - return $this->lastResponse->getBody(); + return $this->lastResponse->getContent(); } /** @@ -234,7 +253,7 @@ protected function retrieveAll($endpoint, array $params = []) try { $data = $this->retrieveData(strval($endpoint), $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } @@ -258,7 +277,11 @@ protected function retrieveAll($endpoint, array $params = []) protected function retrieveData(string $endpoint, array $params = []): array { if (empty($params)) { - $this->lastResponse = $this->getHttpClient()->request('GET', strval($endpoint)); + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( + 'GET', + strval($endpoint), + $this->getContentTypeFromPath(strval($endpoint)) + )); return $this->getResponseAsArray($this->lastResponse); } @@ -287,10 +310,11 @@ protected function retrieveData(string $endpoint, array $params = []): array $params['limit'] = $_limit; $params['offset'] = $offset; - $this->lastResponse = $this->getHttpClient()->request( + $this->lastResponse = $this->getHttpClient()->request($this->createRequest( 'GET', - PathSerializer::create($endpoint, $params)->getPath() - ); + PathSerializer::create($endpoint, $params)->getPath(), + $this->getContentTypeFromPath($endpoint) + )); $newDataSet = $this->getResponseAsArray($this->lastResponse); @@ -368,7 +392,7 @@ protected function attachCustomFieldXML(SimpleXMLElement $xml, array $fields) */ private function getResponseAsArray(Response $response): array { - $body = $response->getBody(); + $body = $response->getContent(); $contentType = $response->getContentType(); $returnData = null; @@ -400,16 +424,16 @@ public function __construct(Client $client, Closure $responseFactory) $this->responseFactory = $responseFactory; } - public function request(string $method, string $path, string $body = ''): Response + public function request(Request $request): Response { - if ($method === 'POST') { - $this->client->requestPost($path, $body); - } elseif ($method === 'PUT') { - $this->client->requestPut($path, $body); - } elseif ($method === 'DELETE') { - $this->client->requestDelete($path); + if ($request->getMethod() === 'POST') { + $this->client->requestPost($request->getPath(), $request->getContent()); + } elseif ($request->getMethod() === 'PUT') { + $this->client->requestPut($request->getPath(), $request->getContent()); + } elseif ($request->getMethod() === 'DELETE') { + $this->client->requestDelete($request->getPath()); } else { - $this->client->requestGet($path); + $this->client->requestGet($request->getPath()); } return ($this->responseFactory)( @@ -445,10 +469,65 @@ public function getContentType(): string return $this->contentType; } - public function getBody(): string + public function getContent(): string { return $this->body; } }; } + + private function createRequest(string $method, string $path, string $contentType, string $content = ''): Request + { + return new class ($method, $path, $contentType, $content) implements Request { + private $method; + private $path; + private $contentType; + private $content; + + public function __construct(string $method, string $path, string $contentType, string $content) + { + $this->method = $method; + $this->path = $path; + $this->contentType = $contentType; + $this->content = $content; + } + + public function getMethod(): string + { + return $this->method; + } + + public function getPath(): string + { + return $this->path; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function getContent(): string + { + return $this->content; + } + }; + } + + private function getContentTypeFromPath(string $path): string + { + $tmp = parse_url($path); + + $path = strtolower($path); + + if (false !== strpos($path, '/uploads.json') || false !== strpos($path, '/uploads.xml')) { + return 'application/octet-stream'; + } elseif ('json' === substr($tmp['path'], -4)) { + return 'application/json'; + } elseif ('xml' === substr($tmp['path'], -3)) { + return 'application/xml'; + } else { + return ''; + } + } } diff --git a/src/Redmine/Api/CustomField.php b/src/Redmine/Api/CustomField.php index 1b9d10c6..fe33074a 100644 --- a/src/Redmine/Api/CustomField.php +++ b/src/Redmine/Api/CustomField.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->customFields = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Group.php b/src/Redmine/Api/Group.php index 4ee6442d..622695d0 100644 --- a/src/Redmine/Api/Group.php +++ b/src/Redmine/Api/Group.php @@ -59,7 +59,7 @@ public function all(array $params = []) try { $this->groups = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Issue.php b/src/Redmine/Api/Issue.php index 3d3264cc..12f7cab5 100644 --- a/src/Redmine/Api/Issue.php +++ b/src/Redmine/Api/Issue.php @@ -110,7 +110,7 @@ public function all(array $params = []) try { return $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/IssueCategory.php b/src/Redmine/Api/IssueCategory.php index d42900f2..6686c908 100644 --- a/src/Redmine/Api/IssueCategory.php +++ b/src/Redmine/Api/IssueCategory.php @@ -69,7 +69,7 @@ public function all($project, array $params = []) try { return $this->listByProject(strval($project), $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/IssuePriority.php b/src/Redmine/Api/IssuePriority.php index e3e777b3..0fbf0523 100644 --- a/src/Redmine/Api/IssuePriority.php +++ b/src/Redmine/Api/IssuePriority.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->issuePriorities = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/IssueRelation.php b/src/Redmine/Api/IssueRelation.php index 113674e4..0084265c 100644 --- a/src/Redmine/Api/IssueRelation.php +++ b/src/Redmine/Api/IssueRelation.php @@ -58,7 +58,7 @@ public function all($issueId, array $params = []) try { $this->relations = $this->listByIssueId($issueId, $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/IssueStatus.php b/src/Redmine/Api/IssueStatus.php index fa6fd577..81dcc4fc 100644 --- a/src/Redmine/Api/IssueStatus.php +++ b/src/Redmine/Api/IssueStatus.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->issueStatuses = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Membership.php b/src/Redmine/Api/Membership.php index 7650ea22..130decf8 100644 --- a/src/Redmine/Api/Membership.php +++ b/src/Redmine/Api/Membership.php @@ -68,7 +68,7 @@ public function all($project, array $params = []) try { $this->memberships = $this->listByProject(strval($project), $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/News.php b/src/Redmine/Api/News.php index e6366e17..5cecbca0 100644 --- a/src/Redmine/Api/News.php +++ b/src/Redmine/Api/News.php @@ -88,7 +88,7 @@ public function all($project = null, array $params = []) $this->news = $this->listByProject(strval($project), $params); } } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Project.php b/src/Redmine/Api/Project.php index 1b49451a..29bbc9f4 100755 --- a/src/Redmine/Api/Project.php +++ b/src/Redmine/Api/Project.php @@ -59,7 +59,7 @@ public function all(array $params = []) try { $this->projects = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Query.php b/src/Redmine/Api/Query.php index 475a551f..7b15316b 100644 --- a/src/Redmine/Api/Query.php +++ b/src/Redmine/Api/Query.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->query = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Role.php b/src/Redmine/Api/Role.php index 67832c89..574f856e 100644 --- a/src/Redmine/Api/Role.php +++ b/src/Redmine/Api/Role.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->roles = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Search.php b/src/Redmine/Api/Search.php index f286b9c6..c287d7c9 100644 --- a/src/Redmine/Api/Search.php +++ b/src/Redmine/Api/Search.php @@ -55,7 +55,7 @@ public function search($query, array $params = []) try { $this->results = $this->listByQuery($query, $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/TimeEntry.php b/src/Redmine/Api/TimeEntry.php index 092f68e3..0d1cae16 100644 --- a/src/Redmine/Api/TimeEntry.php +++ b/src/Redmine/Api/TimeEntry.php @@ -57,7 +57,7 @@ public function all(array $params = []) try { $this->timeEntries = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/TimeEntryActivity.php b/src/Redmine/Api/TimeEntryActivity.php index 871fe789..cc2b8264 100644 --- a/src/Redmine/Api/TimeEntryActivity.php +++ b/src/Redmine/Api/TimeEntryActivity.php @@ -51,7 +51,7 @@ public function all(array $params = []) try { $this->timeEntryActivities = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Tracker.php b/src/Redmine/Api/Tracker.php index 85f3507c..c69418a4 100644 --- a/src/Redmine/Api/Tracker.php +++ b/src/Redmine/Api/Tracker.php @@ -55,7 +55,7 @@ public function all(array $params = []) try { $this->trackers = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/User.php b/src/Redmine/Api/User.php index ca719a70..f82784c2 100644 --- a/src/Redmine/Api/User.php +++ b/src/Redmine/Api/User.php @@ -58,7 +58,7 @@ public function all(array $params = []) try { $this->users = $this->list($params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Version.php b/src/Redmine/Api/Version.php index 3252c985..d5ec42f9 100644 --- a/src/Redmine/Api/Version.php +++ b/src/Redmine/Api/Version.php @@ -67,7 +67,7 @@ public function all($project, array $params = []) try { $this->versions = $this->listByProject(strval($project), $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Api/Wiki.php b/src/Redmine/Api/Wiki.php index 7a066978..24599129 100644 --- a/src/Redmine/Api/Wiki.php +++ b/src/Redmine/Api/Wiki.php @@ -67,7 +67,7 @@ public function all($project, array $params = []) try { $this->wikiPages = $this->listByProject(strval($project), $params); } catch (Exception $e) { - if ($this->getLastResponse()->getBody() === '') { + if ($this->getLastResponse()->getContent() === '') { return false; } diff --git a/src/Redmine/Http/HttpClient.php b/src/Redmine/Http/HttpClient.php index 0cc74fc4..365e0a47 100644 --- a/src/Redmine/Http/HttpClient.php +++ b/src/Redmine/Http/HttpClient.php @@ -14,15 +14,15 @@ * * The client is responsible for ensuring that all data is sent in the correct form and * that received data is processed correctly. + * + * @internal */ interface HttpClient { /** * Create and send a HTTP request and return the response * - * @param string $body must be empty string on 'GET' request - * * @throws ClientException If anything goes wrong on creating or sending the request */ - public function request(string $method, string $path, string $body = ''): Response; + public function request(Request $request): Response; } diff --git a/src/Redmine/Http/Request.php b/src/Redmine/Http/Request.php new file mode 100644 index 00000000..c2f9d528 --- /dev/null +++ b/src/Redmine/Http/Request.php @@ -0,0 +1,36 @@ +getMockBuilder(HttpClient::class)->getMock(); + $mock->expects($testCase->exactly(count($dataSets)))->method('request'); + + $client = new self($testCase, $mock); + + foreach ($dataSets as $data) { + $client->assertRequestData(...$data); + } + + return $client; + } + + private $testCase; + private $client; + private $fifoStack = []; + + private function __construct(TestCase $testCase, HttpClient $client) + { + $this->testCase = $testCase; + $this->client = $client; + } + + private function assertRequestData( + string $method, + string $path, + string $contentType, + string $content = '', + int $responseCode = 200, + string $responseContentType = '', + string $responseContent = '' + ) { + if ($responseContentType === '') { + $responseContentType = $contentType; + } + + array_push($this->fifoStack, [ + 'method' => $method, + 'path' => $path, + 'contentType' => $contentType, + 'content' => $content, + 'responseCode' => $responseCode, + 'responseContentType' => $responseContentType, + 'responseContent' => $responseContent, + ]); + } + + public function request(Request $request): Response + { + $this->client->request($request); + + $data = array_shift($this->fifoStack); + + if (! is_array($data)) { + throw new \Exception(sprintf( + 'Mssing request data for Request "%s %s" with Content-Type "%s".', + $request->getMethod(), + $request->getPath(), + $request->getContentType() + )); + } + + $this->testCase->assertSame($data['method'], $request->getMethod()); + $this->testCase->assertSame($data['path'], $request->getPath()); + $this->testCase->assertSame($data['contentType'], $request->getContentType()); + + if ($data['content'] !== '' && $data['contentType'] === 'application/xml') { + $this->testCase->assertXmlStringEqualsXmlString($data['content'], $request->getContent()); + } elseif ($data['content'] !== '' && $data['contentType'] === 'application/json') { + $this->testCase->assertJsonStringEqualsJsonString($data['content'], $request->getContent()); + } else { + $this->testCase->assertSame($data['content'], $request->getContent()); + } + + /** @var \PHPUnit\Framework\MockObject\MockObject&Response */ + $response = $this->testCase->getMockBuilder(Response::class)->getMock(); + + $response->method('getStatusCode')->willReturn($data['responseCode']); + $response->method('getContentType')->willReturn($data['responseContentType']); + $response->method('getContent')->willReturn($data['responseContent']); + + return $response; + } +} diff --git a/tests/Unit/Api/AbstractApi/DeleteTest.php b/tests/Unit/Api/AbstractApi/DeleteTest.php index d53d5c0e..ca38f22f 100644 --- a/tests/Unit/Api/AbstractApi/DeleteTest.php +++ b/tests/Unit/Api/AbstractApi/DeleteTest.php @@ -7,10 +7,8 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\AbstractApi; use Redmine\Client\Client; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; -use SimpleXMLElement; /** * @covers \Redmine\Api\AbstractApi::delete @@ -19,13 +17,18 @@ class DeleteTest extends TestCase { public function testDeleteWithHttpClient() { - $response = $this->createMock(Response::class); - $response->expects($this->any())->method('getStatusCode')->willReturn(200); - $response->expects($this->any())->method('getContentType')->willReturn('application/xml'); - $response->expects($this->any())->method('getBody')->willReturn(''); - - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1))->method('request')->with('DELETE', 'path.xml', '')->willReturn($response); + $client = AssertingHttpClient::create( + $this, + [ + 'DELETE', + 'path.xml', + 'application/xml', + '', + 200, + 'application/xml', + '' + ] + ); $api = new class ($client) extends AbstractApi {}; diff --git a/tests/Unit/Api/AbstractApi/GetTest.php b/tests/Unit/Api/AbstractApi/GetTest.php index 29750b59..9a49a7c3 100644 --- a/tests/Unit/Api/AbstractApi/GetTest.php +++ b/tests/Unit/Api/AbstractApi/GetTest.php @@ -7,8 +7,7 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\AbstractApi; use Redmine\Client\Client; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; use SimpleXMLElement; @@ -19,13 +18,18 @@ class GetTest extends TestCase { public function testGetWithHttpClient() { - $response = $this->createMock(Response::class); - $response->expects($this->any())->method('getStatusCode')->willReturn(200); - $response->expects($this->any())->method('getContentType')->willReturn('application/json'); - $response->expects($this->any())->method('getBody')->willReturn('{"foo_bar": 12345}'); - - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1))->method('request')->with('GET', 'path.json')->willReturn($response); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + 'path.json', + 'application/json', + '', + 200, + 'application/json', + '{"foo_bar": 12345}' + ] + ); $api = new class ($client) extends AbstractApi {}; diff --git a/tests/Unit/Api/AbstractApi/PostTest.php b/tests/Unit/Api/AbstractApi/PostTest.php index 7f08ea62..8facc959 100644 --- a/tests/Unit/Api/AbstractApi/PostTest.php +++ b/tests/Unit/Api/AbstractApi/PostTest.php @@ -7,8 +7,7 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\AbstractApi; use Redmine\Client\Client; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; use SimpleXMLElement; @@ -19,13 +18,18 @@ class PostTest extends TestCase { public function testPostWithHttpClient() { - $response = $this->createMock(Response::class); - $response->expects($this->any())->method('getStatusCode')->willReturn(200); - $response->expects($this->any())->method('getContentType')->willReturn('application/xml'); - $response->expects($this->any())->method('getBody')->willReturn(''); - - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1))->method('request')->with('POST', 'path.xml', '')->willReturn($response); + $client = AssertingHttpClient::create( + $this, + [ + 'POST', + 'path.xml', + 'application/xml', + '', + 200, + 'application/xml', + '' + ] + ); $api = new class ($client) extends AbstractApi {}; diff --git a/tests/Unit/Api/AbstractApi/PutTest.php b/tests/Unit/Api/AbstractApi/PutTest.php index 41071ecb..61656088 100644 --- a/tests/Unit/Api/AbstractApi/PutTest.php +++ b/tests/Unit/Api/AbstractApi/PutTest.php @@ -7,8 +7,7 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\AbstractApi; use Redmine\Client\Client; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; use SimpleXMLElement; @@ -19,13 +18,18 @@ class PutTest extends TestCase { public function testPutWithHttpClient() { - $response = $this->createMock(Response::class); - $response->expects($this->any())->method('getStatusCode')->willReturn(200); - $response->expects($this->any())->method('getContentType')->willReturn('application/xml'); - $response->expects($this->any())->method('getBody')->willReturn(''); - - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1))->method('request')->with('PUT', 'path.xml', '')->willReturn($response); + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + 'path.xml', + 'application/xml', + '', + 200, + 'application/xml', + '' + ] + ); $api = new class ($client) extends AbstractApi {}; diff --git a/tests/Unit/Api/AbstractApiTest.php b/tests/Unit/Api/AbstractApiTest.php index 4cb0c355..b3683e9c 100644 --- a/tests/Unit/Api/AbstractApiTest.php +++ b/tests/Unit/Api/AbstractApiTest.php @@ -9,6 +9,7 @@ use Redmine\Exception\SerializerException; use Redmine\Http\HttpClient; use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use ReflectionMethod; use SimpleXMLElement; @@ -107,17 +108,23 @@ public static function getIsNotNullReturnsCorrectBooleanData(): array */ public function testLastCallFailedPreventsRaceCondition() { - $response1 = $this->createMock(Response::class); - $response1->method('getStatusCode')->willReturn(200); - - $response2 = $this->createMock(Response::class); - $response2->method('getStatusCode')->willReturn(500); - - $client = $this->createMock(HttpClient::class); - $client->method('request')->willReturnMap([ - ['GET', '200.json', '', $response1], - ['GET', '500.json', '', $response2], - ]); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '200.json', + 'application/json', + '', + 200 + ], + [ + 'GET', + '500.json', + 'application/json', + '', + 500 + ] + ); $api1 = new class ($client) extends AbstractApi { public function __construct($client) diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php index 7e34cdce..6cc1ef3c 100644 --- a/tests/Unit/Api/Group/CreateTest.php +++ b/tests/Unit/Api/Group/CreateTest.php @@ -8,7 +8,7 @@ use Redmine\Api\Group; use Redmine\Exception\MissingParameterException; use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use SimpleXMLElement; /** @@ -18,22 +18,18 @@ class CreateTest extends TestCase { public function testCreateWithNameCreatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('POST', $method); - $this->assertSame('/groups.xml', $path); - $this->assertXmlStringEqualsXmlString('Group Name', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'POST', + '/groups.xml', + 'application/xml', + 'Group Name', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); @@ -50,22 +46,18 @@ public function testCreateWithNameCreatesGroup() public function testCreateWithNameAndUserIdsCreatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('POST', $method); - $this->assertSame('/groups.xml', $path); - $this->assertXmlStringEqualsXmlString('Group Name123', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'POST', + '/groups.xml', + 'application/xml', + 'Group Name123', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); @@ -82,22 +74,18 @@ public function testCreateWithNameAndUserIdsCreatesGroup() public function testCreateWithNameAndCustomFieldsCreatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('POST', $method); - $this->assertSame('/groups.xml', $path); - $this->assertXmlStringEqualsXmlString('Group Name5', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'POST', + '/groups.xml', + 'application/xml', + 'Group Name5', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); diff --git a/tests/Unit/Api/Group/UpdateTest.php b/tests/Unit/Api/Group/UpdateTest.php index 08bdaed6..e75ac708 100644 --- a/tests/Unit/Api/Group/UpdateTest.php +++ b/tests/Unit/Api/Group/UpdateTest.php @@ -6,9 +6,7 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\Group; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; -use SimpleXMLElement; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @covers \Redmine\Api\Group::update @@ -17,22 +15,18 @@ class UpdateTest extends TestCase { public function testUpdateWithNameUpdatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/groups/1.xml', $path); - $this->assertXmlStringEqualsXmlString('Group Name', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/groups/1.xml', + 'application/xml', + 'Group Name', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); @@ -45,22 +39,18 @@ public function testUpdateWithNameUpdatesGroup() public function testUpdateWithUserIdsUpdatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/groups/1.xml', $path); - $this->assertXmlStringEqualsXmlString('123', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/groups/1.xml', + 'application/xml', + '123', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); @@ -73,22 +63,18 @@ public function testUpdateWithUserIdsUpdatesGroup() public function testUpdateWithCustomFieldsUpdatesGroup() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/groups/1.xml', $path); - $this->assertXmlStringEqualsXmlString('5', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/groups/1.xml', + 'application/xml', + '5', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Group($client); diff --git a/tests/Unit/Api/IssueTest.php b/tests/Unit/Api/IssueTest.php index 582e8c38..c9607de5 100644 --- a/tests/Unit/Api/IssueTest.php +++ b/tests/Unit/Api/IssueTest.php @@ -5,8 +5,7 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\Issue; use Redmine\Client\Client; -use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; use Redmine\Tests\Fixtures\MockClient; use SimpleXMLElement; @@ -516,38 +515,27 @@ public function testCreateWithClientCleansParameters() */ public function testCreateWithHttpClientRetrievesIssueStatusId() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/issue_statuses.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"issue_statuses":[{"name":"Status Name","id":123}]}', - ] - ); - } - - if ($method === 'POST') { - $this->assertSame('/issues.xml', $path); - $this->assertXmlStringEqualsXmlString('123', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + '{"issue_statuses":[{"name":"Status Name","id":123}]}' + ], + [ + 'POST', + '/issues.xml', + 'application/xml', + '123', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); @@ -570,38 +558,27 @@ public function testCreateWithHttpClientRetrievesIssueStatusId() */ public function testCreateWithHttpClientRetrievesProjectId() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/projects.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"projects":[{"name":"Project Name","id":3}]}', - ] - ); - } - - if ($method === 'POST') { - $this->assertSame('/issues.xml', $path); - $this->assertXmlStringEqualsXmlString('3', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects.json', + 'application/json', + '', + 200, + 'application/json', + '{"projects":[{"name":"Project Name","id":3}]}' + ], + [ + 'POST', + '/issues.xml', + 'application/xml', + '3', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); @@ -624,38 +601,27 @@ public function testCreateWithHttpClientRetrievesProjectId() */ public function testCreateWithHttpClientRetrievesIssueCategoryId() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/projects/3/issue_categories.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"issue_categories":[{"name":"Category Name","id":45}]}', - ] - ); - } - - if ($method === 'POST') { - $this->assertSame('/issues.xml', $path); - $this->assertXmlStringEqualsXmlString('345', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/3/issue_categories.json', + 'application/json', + '', + 200, + 'application/json', + '{"issue_categories":[{"name":"Category Name","id":45}]}' + ], + [ + 'POST', + '/issues.xml', + 'application/xml', + '345', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); @@ -678,38 +644,27 @@ public function testCreateWithHttpClientRetrievesIssueCategoryId() */ public function testCreateWithHttpClientRetrievesTrackerId() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/trackers.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"trackers":[{"name":"Tracker Name","id":9}]}', - ] - ); - } - - if ($method === 'POST') { - $this->assertSame('/issues.xml', $path); - $this->assertXmlStringEqualsXmlString('9', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/trackers.json', + 'application/json', + '', + 200, + 'application/json', + '{"trackers":[{"name":"Tracker Name","id":9}]}' + ], + [ + 'POST', + '/issues.xml', + 'application/xml', + '9', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); @@ -732,38 +687,27 @@ public function testCreateWithHttpClientRetrievesTrackerId() */ public function testCreateWithHttpClientRetrievesUserId() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/users.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"users":[{"login":"Author Name","id":5},{"login":"Assigned to User Name","id":6}]}', - ] - ); - } - - if ($method === 'POST') { - $this->assertSame('/issues.xml', $path); - $this->assertXmlStringEqualsXmlString('65', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/users.json', + 'application/json', + '', + 200, + 'application/json', + '{"users":[{"login":"Author Name","id":5},{"login":"Assigned to User Name","id":6}]}' + ], + [ + 'POST', + '/issues.xml', + 'application/xml', + '65', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); @@ -994,38 +938,27 @@ public function testSetIssueStatusWithClient() */ public function testSetIssueStatusWithHttpClient() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(2)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - if ($method === 'GET') { - $this->assertSame('/issue_statuses.json', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/json', - 'getBody' => '{"issue_statuses":[{"name":"Status Name","id":123}]}', - ] - ); - } - - if ($method === 'PUT') { - $this->assertSame('/issues/5.xml', $path); - $this->assertXmlStringEqualsXmlString('5123', $body); - - return $this->createConfiguredMock( - Response::class, - [ - 'getContentType' => 'application/xml', - 'getBody' => '', - ] - ); - } - - throw new \Exception(); - }); + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/issue_statuses.json', + 'application/json', + '', + 200, + 'application/json', + '{"issue_statuses":[{"name":"Status Name","id":123}]}' + ], + [ + 'PUT', + '/issues/5.xml', + 'application/xml', + '5123', + 200, + 'application/xml', + '' + ] + ); // Create the object under test $api = new Issue($client); diff --git a/tests/Unit/Api/Project/ArchiveTest.php b/tests/Unit/Api/Project/ArchiveTest.php index ed7622e3..25c1bb77 100644 --- a/tests/Unit/Api/Project/ArchiveTest.php +++ b/tests/Unit/Api/Project/ArchiveTest.php @@ -1,5 +1,7 @@ createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/archive.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 204, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/archive.xml', + 'application/xml', + '', + 204 + ] + ); $api = new Project($client); @@ -39,21 +36,16 @@ public function testArchiveReturnsTrue() public function testArchiveThrowsUnexpectedResponseException() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/archive.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 403, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/archive.xml', + 'application/xml', + '', + 403 + ] + ); $api = new Project($client); diff --git a/tests/Unit/Api/Project/CloseTest.php b/tests/Unit/Api/Project/CloseTest.php index 24cfc0ac..4067b769 100644 --- a/tests/Unit/Api/Project/CloseTest.php +++ b/tests/Unit/Api/Project/CloseTest.php @@ -8,6 +8,7 @@ use Redmine\Exception\UnexpectedResponseException; use Redmine\Http\HttpClient; use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @covers \Redmine\Api\Project::close @@ -16,21 +17,16 @@ class CloseTest extends TestCase { public function testCloseReturnsTrue() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/close.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 204, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/close.xml', + 'application/xml', + '', + 204 + ] + ); $api = new Project($client); @@ -39,21 +35,16 @@ public function testCloseReturnsTrue() public function testCloseThrowsUnexpectedResponseException() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/close.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 403, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/close.xml', + 'application/xml', + '', + 403 + ] + ); $api = new Project($client); diff --git a/tests/Unit/Api/Project/ReopenTest.php b/tests/Unit/Api/Project/ReopenTest.php index ca5072ba..5e64785f 100644 --- a/tests/Unit/Api/Project/ReopenTest.php +++ b/tests/Unit/Api/Project/ReopenTest.php @@ -7,7 +7,7 @@ use Redmine\Api\Project; use Redmine\Exception\UnexpectedResponseException; use Redmine\Http\HttpClient; -use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @covers \Redmine\Api\Project::reopen @@ -16,21 +16,16 @@ class ReopenTest extends TestCase { public function testReopenReturnsTrue() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/reopen.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 204, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/reopen.xml', + 'application/xml', + '', + 204 + ] + ); $api = new Project($client); @@ -39,21 +34,16 @@ public function testReopenReturnsTrue() public function testReopenThrowsUnexpectedResponseException() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/reopen.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 403, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/reopen.xml', + 'application/xml', + '', + 403 + ] + ); $api = new Project($client); diff --git a/tests/Unit/Api/Project/UnarchiveTest.php b/tests/Unit/Api/Project/UnarchiveTest.php index 45d7e901..84a16568 100644 --- a/tests/Unit/Api/Project/UnarchiveTest.php +++ b/tests/Unit/Api/Project/UnarchiveTest.php @@ -8,6 +8,7 @@ use Redmine\Exception\UnexpectedResponseException; use Redmine\Http\HttpClient; use Redmine\Http\Response; +use Redmine\Tests\Fixtures\AssertingHttpClient; /** * @covers \Redmine\Api\Project::unarchive @@ -16,21 +17,16 @@ class UnarchiveTest extends TestCase { public function testUnarchiveReturnsTrue() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/unarchive.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 204, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/unarchive.xml', + 'application/xml', + '', + 204 + ] + ); $api = new Project($client); @@ -39,21 +35,16 @@ public function testUnarchiveReturnsTrue() public function testUnarchiveThrowsUnexpectedResponseException() { - $client = $this->createMock(HttpClient::class); - $client->expects($this->exactly(1)) - ->method('request') - ->willReturnCallback(function (string $method, string $path, string $body = '') { - $this->assertSame('PUT', $method); - $this->assertSame('/projects/5/unarchive.xml', $path); - $this->assertSame('', $body); - - return $this->createConfiguredMock(Response::class, [ - 'getStatusCode' => 403, - 'getContentType' => 'application/xml', - 'getBody' => '', - ]); - }) - ; + $client = AssertingHttpClient::create( + $this, + [ + 'PUT', + '/projects/5/unarchive.xml', + 'application/xml', + '', + 403 + ] + ); $api = new Project($client);