diff --git a/CHANGELOG.md b/CHANGELOG.md index 451382d8..cc02eafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/kbsali/php-redmine-api/compare/v2.5.0...v2.x) +### Added + +- New method `Redmine\Api\...::getLastResponse()` to get the last response made by the API class. + ### Fixed - Parameter types for IDs were fixed in API for attachments, groups, issues, project, users and versions. diff --git a/composer.json b/composer.json index 91c2eb24..1a5dd14b 100644 --- a/composer.json +++ b/composer.json @@ -29,11 +29,12 @@ "psr/http-factory": "^1.0" }, "require-dev": { + "behat/behat": "^3.14", "friendsofphp/php-cs-fixer": "^3.45", - "phpunit/phpunit": "^9 || ^10.5", "guzzlehttp/psr7": "^2", "php-mock/php-mock-phpunit": "^2.6", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9 || ^10.5" }, "autoload": { "psr-4": { @@ -45,10 +46,13 @@ "Redmine\\Tests\\": "tests/" } }, + "config": { + "sort-packages": true + }, "scripts": { + "behat": "behat --config tests/Behat/behat.yml", "codestyle": "php-cs-fixer fix", "coverage": "phpunit --coverage-html=\".phpunit.cache/code-coverage\"", - "end2end": "phpunit --configuration=\"phpunit-end2end.xml\"", "phpstan": "phpstan analyze --memory-limit 512M --configuration .phpstan.neon", "phpunit": "phpunit", "test": [ diff --git a/docker-compose.yml b/docker-compose.yml index a9e5926a..11861779 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: redmine-dev: image: redmine:5.1.1 + user: "1000:1000" ports: - "3000:3000" environment: @@ -27,6 +28,7 @@ services: redmine-50101: image: redmine:5.1.1 + user: "1000:1000" ports: - "5101:3000" environment: @@ -38,6 +40,7 @@ services: redmine-50007: image: redmine:5.0.7 + user: "1000:1000" ports: - "5007:3000" environment: diff --git a/phpunit-end2end.xml b/phpunit-end2end.xml deleted file mode 100644 index da0e4992..00000000 --- a/phpunit-end2end.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - src/Redmine/ - - - - - tests/End2End/ - - - - - - diff --git a/src/Redmine/Api/AbstractApi.php b/src/Redmine/Api/AbstractApi.php index 2cff81e0..d2b64e8e 100644 --- a/src/Redmine/Api/AbstractApi.php +++ b/src/Redmine/Api/AbstractApi.php @@ -72,7 +72,7 @@ final protected function getHttpClient(): HttpClient return $this->httpClient; } - final protected function getLastResponse(): Response + final public function getLastResponse(): Response { return $this->lastResponse !== null ? $this->lastResponse : HttpFactory::makeResponse(0, '', ''); } diff --git a/src/Redmine/Api/Project.php b/src/Redmine/Api/Project.php index d0663fcb..67e5f39b 100755 --- a/src/Redmine/Api/Project.php +++ b/src/Redmine/Api/Project.php @@ -347,7 +347,7 @@ protected function prepareParamsXml($params) { @trigger_error('`' . __METHOD__ . '()` is deprecated since v2.3.0, use `\Redmine\Serializer\XmlSerializer::createFromArray()` instead.', E_USER_DEPRECATED); - return new \SimpleXMLElement( + return new SimpleXMLElement( XmlSerializer::createFromArray(['project' => $params])->getEncoded() ); } diff --git a/src/Redmine/Http/Response.php b/src/Redmine/Http/Response.php index 0370fa00..cde231d4 100644 --- a/src/Redmine/Http/Response.php +++ b/src/Redmine/Http/Response.php @@ -9,8 +9,6 @@ * * The method signatures are defined with the intention that an implementing class * can implment this interface and also the PSR-7 `\Psr\Http\Message\ResponseInterface` - * - * @internal */ interface Response { diff --git a/tests/Behat/Bootstrap/AttachmentContextTrait.php b/tests/Behat/Bootstrap/AttachmentContextTrait.php new file mode 100644 index 00000000..7b234891 --- /dev/null +++ b/tests/Behat/Bootstrap/AttachmentContextTrait.php @@ -0,0 +1,48 @@ +getNativeCurlClient()->getApi('attachment'); + + $this->registerClientResponse( + $api->upload(file_get_contents($filepath), $data), + $api->getLastResponse() + ); + } + + /** + * @When I show the attachment with the id :attachmentId + */ + public function iShowTheAttachmentWithTheId(int $attachmentId) + { + /** @var Attachment */ + $api = $this->getNativeCurlClient()->getApi('attachment'); + + $this->registerClientResponse( + $api->show($attachmentId), + $api->getLastResponse() + ); + } +} diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php new file mode 100644 index 00000000..b5e083c4 --- /dev/null +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -0,0 +1,356 @@ +hook($scope); + } + + /** + * @AfterScenario + */ + public static function reset(AfterScenarioScope $scope) + { + static::$tracer->hook($scope); + } + + /** + * @AfterSuite + */ + public static function clean(AfterSuiteScope $scope) + { + static::$tracer->hook($scope); + static::$tracer = null; + } + + private RedmineInstance $redmine; + + private NativeCurlClient $client; + + private Response $lastResponse; + + private mixed $lastReturn; + + private array $lastReturnAsArray; + + public function __construct(string $redmineVersion) + { + $version = RedmineVersion::tryFrom($redmineVersion); + + if ($version === null) { + throw new InvalidArgumentException('Redmine ' . $redmineVersion . ' is not supported.'); + } + + $this->redmine = static::$tracer::getRedmineInstance($version); + + parent::__construct('BehatRedmine' . $version->asId()); + } + + /** + * @Given I have a :clientName client + */ + public function iHaveAClient($clientName) + { + if ($clientName !== 'NativeCurlClient') { + throw new InvalidArgumentException('Client ' . $clientName . ' is not supported.'); + } + + $this->client = new NativeCurlClient( + $this->redmine->getRedmineUrl(), + $this->redmine->getApiKey() + ); + } + + private function getNativeCurlClient(): NativeCurlClient + { + return $this->client; + } + + private function registerClientResponse(mixed $lastReturn, Response $lastResponse): void + { + unset($this->lastReturnAsArray); + $this->lastReturn = $lastReturn; + $this->lastResponse = $lastResponse; + } + + /** + * @Then the response has the status code :statusCode + */ + public function theResponseHasTheStatusCode(int $statusCode) + { + $this->assertSame( + $statusCode, + $this->lastResponse->getStatusCode(), + 'Raw response content: ' . $this->lastResponse->getContent() + ); + } + + /** + * @Then the response has the content type :contentType + */ + public function theResponseHasTheContentType(string $contentType) + { + $this->assertStringStartsWith( + $contentType, + $this->lastResponse->getContentType(), + 'Raw response content: ' . $this->lastResponse->getContent() + ); + } + + /** + * @Then the response has an empty content type + */ + public function theResponseHasAnEmptyContentType() + { + $this->assertSame('', $this->lastResponse->getContentType()); + } + + /** + * @Then the response has the content :content + */ + public function theResponseHasTheContent(string $content) + { + $this->assertSame($content, $this->lastResponse->getContent()); + } + + /** + * @Then the returned data is true + */ + public function theReturnedDataIsTrue() + { + $this->assertTrue($this->lastReturn); + } + + /** + * @Then the returned data is false + */ + public function theReturnedDataIsFalse() + { + $this->assertFalse($this->lastReturn); + } + + /** + * @Then the returned data is exactly :content + */ + public function theReturnedDataIsExactly(string $content) + { + $this->assertSame($content, $this->lastReturn); + } + + /** + * @Then the returned data is an instance of :className + */ + public function theReturnedDataIsAnInstanceOf(string $className) + { + $this->assertInstanceOf($className, $this->lastReturn); + } + + /** + * @Then the returned data has only the following properties + */ + public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $string) + { + $this->theReturnedDataPropertyHasOnlyTheFollowingProperties(null, $string); + } + + /** + * @Then the returned data has proterties with the following data + */ + public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $table) + { + $this->theReturnedDataPropertyContainsTheFollowingData(null, $table); + } + + /** + * @Then the returned data :property property is an array + */ + public function theReturnedDataPropertyIsAnArray($property) + { + $returnData = $this->getLastReturnAsArray(); + + $value = $this->getItemFromArray($returnData, $property); + + $this->assertIsArray($value); + } + + /** + * @Then the returned data :property property contains :count items + */ + public function theReturnedDataPropertyContainsItems($property, int $count) + { + $returnData = $this->getLastReturnAsArray(); + + $value = $this->getItemFromArray($returnData, $property); + + $this->assertCount($count, $value); + } + + /** + * @Then the returned data :property property contains the following data + */ + public function theReturnedDataPropertyContainsTheFollowingData($property, TableNode $table) + { + $returnData = $this->getItemFromArray($this->getLastReturnAsArray(), $property); + + if (! is_array($returnData)) { + throw new RuntimeException('The returned data on property "' . $property . '" is not an array.'); + } + + foreach ($table as $row) { + $this->assertArrayHasKey($row['property'], $returnData); + + $value = $returnData[$row['property']]; + + if ($value instanceof SimpleXMLElement) { + $value = strval($value); + } + + $expected = $row['value']; + + // Handle expected empty array + if ($value === [] && $expected === '[]') { + $expected = []; + } + + // Handle expected int values + if (is_int($value) && ctype_digit($expected)) { + $expected = intval($expected); + } + + // Handle expected null value + if ($value === null && $expected === 'null') { + $expected = null; + } + + // Handle expected true value + if ($value === true && $expected === 'true') { + $expected = true; + } + + // Handle expected false value + if ($value === false && $expected === 'false') { + $expected = false; + } + + // Handle placeholder %redmine_id% + if (is_string($expected)) { + $expected = str_replace('%redmine_id%', strval($this->redmine->getVersionId()), $expected); + } + + $this->assertSame($expected, $value, 'Error with property "' . $row['property'] . '"'); + } + } + + /** + * @Then the returned data :property property has only the following properties + */ + public function theReturnedDataPropertyHasOnlyTheFollowingProperties($property, PyStringNode $string) + { + $value = $this->getItemFromArray($this->getLastReturnAsArray(), $property); + + $properties = array_keys($value); + + $this->assertSame($string->getStrings(), $properties); + } + + /** + * @Then the returned data :property property has only the following properties with Redmine version :versionComparision + */ + public function theReturnedDataPropertyHasOnlyTheFollowingPropertiesWithRedmineVersion($property, string $versionComparision, PyStringNode $string) + { + $parts = explode(' ', $versionComparision); + + $redmineVersion = RedmineVersion::tryFrom($parts[1]); + + if ($redmineVersion === null) { + throw new InvalidArgumentException('Comparison with Redmine ' . $versionComparision . ' is not supported.'); + } + + if (version_compare($this->redmine->getVersionString(), $parts[1], $parts[0])) { + $this->theReturnedDataPropertyHasOnlyTheFollowingProperties($property, $string); + } + } + + private function getLastReturnAsArray(): array + { + if (isset($this->lastReturnAsArray)) { + return $this->lastReturnAsArray; + } + + $returnData = null; + + if ($this->lastReturn instanceof SimpleXMLElement) { + $returnData = json_decode(json_encode($this->lastReturn), true); + } elseif (is_string($this->lastReturn)) { + $returnData = json_decode($this->lastReturn, true); + } elseif (is_array($this->lastReturn)) { + $returnData = $this->lastReturn; + } + + if (! is_array($returnData)) { + throw new RuntimeException(sprintf( + 'the last returned data "%s" could not parsed into an array.', + json_encode($this->lastReturn), + )); + } + + $this->lastReturnAsArray = $returnData; + + return $this->lastReturnAsArray; + } + + /** + * Get item from an array by key supporting "dot" notation. + */ + private function getItemFromArray(array $array, $key): mixed + { + if ($key === null) { + return $array; + } + + foreach (explode('.', $key) as $segment) { + if (! array_key_exists($segment, $array)) { + return null; + } + + $array = $array[$segment]; + } + + return $array; + } +} diff --git a/tests/Behat/Bootstrap/GroupContextTrait.php b/tests/Behat/Bootstrap/GroupContextTrait.php new file mode 100644 index 00000000..a4110626 --- /dev/null +++ b/tests/Behat/Bootstrap/GroupContextTrait.php @@ -0,0 +1,93 @@ +iCreateAGroupWithTheFollowingData($table); + } + + /** + * @When I create a group with the following data + */ + public function iCreateAGroupWithTheFollowingData(TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['property']] = $row['value']; + } + + /** @var Group */ + $api = $this->getNativeCurlClient()->getApi('group'); + + $this->registerClientResponse( + $api->create($data), + $api->getLastResponse() + ); + } + + /** + * @When I list all groups + */ + public function iListAllGroups() + { + /** @var Group */ + $api = $this->getNativeCurlClient()->getApi('group'); + + $this->registerClientResponse( + $api->list(), + $api->getLastResponse() + ); + } + + /** + * @When I show the group with id :groupId + */ + public function iShowTheGroupWithId(int $groupId) + { + /** @var Group */ + $api = $this->getNativeCurlClient()->getApi('group'); + + $this->registerClientResponse( + $api->show($groupId), + $api->getLastResponse() + ); + } + + /** + * @When I update the group with id :groupId with the following data + */ + public function iUpdateTheGroupWithIdWithTheFollowingData(int $groupId, TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['property']] = $row['value']; + } + + /** @var Group */ + $api = $this->getNativeCurlClient()->getApi('group'); + + $this->registerClientResponse( + $api->update($groupId, $data), + $api->getLastResponse() + ); + } +} diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php new file mode 100644 index 00000000..ad037bdd --- /dev/null +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -0,0 +1,150 @@ +iCreateAProjectWithTheFollowingData($table); + } + + /** + * @When I create a project with the following data + */ + public function iCreateAProjectWithTheFollowingData(TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['property']] = $row['value']; + } + + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->create($data), + $api->getLastResponse() + ); + } + + /** + * @When I list all projects + */ + public function iListAllProjects() + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->list(), + $api->getLastResponse() + ); + } + + /** + * @When I show the project with identifier :identifier + */ + public function iShowTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->show($identifier), + $api->getLastResponse() + ); + } + + /** + * @When I update the project with identifier :identifier with the following data + */ + public function iUpdateTheProjectWithIdentifierWithTheFollowingData(string $identifier, TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['property']] = $row['value']; + } + + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->update($identifier, $data), + $api->getLastResponse() + ); + } + + /** + * @When I close the project with identifier :identifier + */ + public function iCloseTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->close($identifier), + $api->getLastResponse() + ); + } + + /** + * @When I reopen the project with identifier :identifier + */ + public function iReopenTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->reopen($identifier), + $api->getLastResponse() + ); + } + + /** + * @When I archive the project with identifier :identifier + */ + public function iArchiveTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->archive($identifier), + $api->getLastResponse() + ); + } + + /** + * @When I unarchive the project with identifier :identifier + */ + public function iUnarchiveTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $api = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $api->unarchive($identifier), + $api->getLastResponse() + ); + } +} diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php new file mode 100644 index 00000000..289f6dfa --- /dev/null +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -0,0 +1,103 @@ +iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData( + $pageName, + $identifier, + new TableNode([['property', 'value']]) + ); + } + + /** + * @When I create a wiki page with name :pageName and project identifier :identifier with the following data + */ + public function iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData(string $pageName, string $identifier, TableNode $table) + { + $data = $this->prepareWikiData($table); + + /** @var Wiki */ + $api = $this->getNativeCurlClient()->getApi('wiki'); + + $this->registerClientResponse( + $api->create($identifier, $pageName, $data), + $api->getLastResponse() + ); + } + + /** + * @When I show the wiki page with name :pageName and project identifier :identifier + */ + public function iShowTheWikiPageWithNameAndProjectIdentifier(string $pageName, string $identifier) + { + /** @var Wiki */ + $api = $this->getNativeCurlClient()->getApi('wiki'); + + $this->registerClientResponse( + $api->show($identifier, $pageName), + $api->getLastResponse() + ); + } + + /** + * @When I update the wiki page with name :pageName and project identifier :identifier with the following data + */ + public function iUpdateTheWikiPageWithNameAndProjectIdentifierWithTheFollowingData(string $pageName, string $identifier, TableNode $table) + { + $data = $this->prepareWikiData($table); + + /** @var Wiki */ + $api = $this->getNativeCurlClient()->getApi('wiki'); + + $this->registerClientResponse( + $api->update($identifier, $pageName, $data), + $api->getLastResponse() + ); + } + + /** + * @When I delete the wiki page with name :pageName and project identifier :identifier + */ + public function iDeleteTheWikiPageWithNameAndProjectIdentifier(string $pageName, string $identifier) + { + /** @var Wiki */ + $api = $this->getNativeCurlClient()->getApi('wiki'); + + $this->registerClientResponse( + $api->remove($identifier, $pageName), + $api->getLastResponse() + ); + } + + private function prepareWikiData(TableNode $table): array + { + $data = []; + + foreach ($table as $row) { + $key = $row['property']; + $value = $row['value']; + + // Support for json in uploads + if ($key === 'uploads') { + $value = json_decode($value, true); + } + + $data[$key] = $value; + } + + return $data; + } +} diff --git a/tests/Behat/behat.yml b/tests/Behat/behat.yml new file mode 100644 index 00000000..cf006462 --- /dev/null +++ b/tests/Behat/behat.yml @@ -0,0 +1,14 @@ +default: + suites: + redmine_50101: + paths: + - '%paths.base%/features' + contexts: + - Redmine\Tests\Behat\Bootstrap\FeatureContext: + redmineVersion: '5.1.1' + redmine_50007: + paths: + - '%paths.base%/features' + contexts: + - Redmine\Tests\Behat\Bootstrap\FeatureContext: + redmineVersion: '5.0.7' diff --git a/tests/Behat/features/attachments.feature b/tests/Behat/features/attachments.feature new file mode 100644 index 00000000..d04c370a --- /dev/null +++ b/tests/Behat/features/attachments.feature @@ -0,0 +1,64 @@ +Feature: Interacting with the REST API for attachments + In order to interact with REST API for attachments + As a user + I want to make sure the Redmine server replies with the correct response + + Scenario: Uploading an attachment + Given I have a "NativeCurlClient" client + When I upload the content of the file "%tests_dir%/Fixtures/testfile_01.txt" with the following data + | property | value | + | filename | testfile.txt | + Then the response has the status code "201" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + upload + """ + And the returned data "upload" property is an array + And the returned data "upload" property has only the following properties + """ + id + token + """ + And the returned data "upload" property contains the following data + | property | value | + | id | 1 | + | token | 1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8 | + + Scenario: Showing the details of an attachment + Given I have a "NativeCurlClient" client + And I upload the content of the file "%tests_dir%/Fixtures/testfile_01.txt" with the following data + | property | value | + | filename | testfile.txt | + When I show the attachment with the id "1" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + attachment + """ + And the returned data "attachment" property is an array + And the returned data "attachment" property has only the following properties + """ + id + filename + filesize + content_type + description + content_url + author + created_on + """ + And the returned data "attachment" property contains the following data + | property | value | + | id | 1 | + | filename | testfile.txt | + | filesize | 65 | + | content_type | text/plain | + | description | null | + | content_url | http://redmine-%redmine_id%:3000/attachments/download/1/testfile.txt | + And the returned data "attachment.author" property is an array + And the returned data "attachment.author" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature new file mode 100644 index 00000000..e6363ae2 --- /dev/null +++ b/tests/Behat/features/groups.feature @@ -0,0 +1,89 @@ +Feature: Interacting with the REST API for groups + In order to interact with REST API for groups + As a user + I want to make sure the Redmine server replies with the correct response + + Scenario: Creating a group with minimal parameters + Given I have a "NativeCurlClient" client + When I create a group with name "Test Group" + Then the response has the status code "201" + And the response has the content type "application/xml" + And the returned data is an instance of "SimpleXMLElement" + And the returned data has only the following properties + """ + id + name + """ + And the returned data has proterties with the following data + | property | value | + | id | 4 | + | name | Test Group | + + Scenario: Listing of zero groups + Given I have a "NativeCurlClient" client + When I list all groups + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + groups + """ + And the returned data "groups" property is an array + And the returned data "groups" property contains "0" items + + Scenario: Listing of one group + Given I have a "NativeCurlClient" client + And I create a group with name "Test Group" + When I list all groups + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + groups + """ + And the returned data "groups" property is an array + And the returned data "groups" property contains "1" items + And the returned data "groups.0" property is an array + And the returned data "groups.0" property has only the following properties + """ + id + name + """ + And the returned data "groups.0" property contains the following data + | property | value | + | id | 4 | + | name | Test Group | + + Scenario: Showing a specific group + Given I have a "NativeCurlClient" client + And I create a group with name "Test Group" + When I show the group with id "4" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + group + """ + And the returned data "group" property is an array + And the returned data "group" property has only the following properties + """ + id + name + """ + And the returned data "group" property contains the following data + | property | value | + | id | 4 | + | name | Test Group | + + Scenario: Updating a group + Given I have a "NativeCurlClient" client + And I create a group with the following data + | property | value | + | name | Test Group | + When I update the group with id "4" with the following data + | property | value | + | name | new group name | + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is exactly "" diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature new file mode 100644 index 00000000..b7079ae3 --- /dev/null +++ b/tests/Behat/features/projects.feature @@ -0,0 +1,222 @@ +Feature: Interacting with the REST API for projects + In order to interact with REST API for projects + As a user + I want to make sure the Redmine server replies with the correct response + + Scenario: Creating a project with minimal parameters + Given I have a "NativeCurlClient" client + When I create a project with name "Test Project" and identifier "test-project" + Then the response has the status code "201" + And the response has the content type "application/xml" + And the returned data is an instance of "SimpleXMLElement" + And the returned data has only the following properties + """ + id + name + identifier + description + homepage + status + is_public + inherit_members + created_on + updated_on + """ + And the returned data has proterties with the following data + | property | value | + | id | 1 | + | name | Test Project | + | identifier | test-project | + + Scenario: Creating a project with multiple parameters + Given I have a "NativeCurlClient" client + When I create a project with the following data + | property | value | + | name | Test Project | + | identifier | test-project | + | description | project description | + | homepage | https://example.com | + Then the response has the status code "201" + And the response has the content type "application/xml" + And the returned data is an instance of "SimpleXMLElement" + And the returned data has proterties with the following data + | property | value | + | id | 1 | + | name | Test Project | + | identifier | test-project | + | description | project description | + | homepage | https://example.com | + | is_public | true | + | inherit_members | false | + + Scenario: Showing a specific project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I show the project with identifier "test-project" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + project + """ + And the returned data "project" property is an array + And the returned data "project" property has only the following properties + """ + id + name + identifier + description + homepage + status + is_public + inherit_members + trackers + issue_categories + created_on + updated_on + """ + And the returned data "project" property contains the following data + | property | value | + | id | 1 | + | name | Test Project | + | identifier | test-project | + | description | null | + | homepage | | + | status | 1 | + | is_public | true | + | inherit_members | false | + | trackers | [] | + | issue_categories | [] | + + Scenario: Listing of zero projects + Given I have a "NativeCurlClient" client + When I list all projects + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + projects + total_count + offset + limit + """ + And the returned data "projects" property is an array + And the returned data "projects" property contains "0" items + And the returned data has proterties with the following data + | property | value | + | total_count | 0 | + | offset | 0 | + | limit | 25 | + + Scenario: Listing of one project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I list all projects + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has proterties with the following data + | property | value | + | total_count | 1 | + | offset | 0 | + | limit | 25 | + And the returned data "projects" property is an array + And the returned data "projects" property contains "1" items + And the returned data "projects.0" property is an array + # field 'homepage' was added in Redmine 5.1.0, see https://www.redmine.org/issues/39113 + And the returned data "projects.0" property has only the following properties with Redmine version ">= 5.1.0" + """ + id + name + identifier + description + homepage + status + is_public + inherit_members + created_on + updated_on + """ + But the returned data "projects.0" property has only the following properties with Redmine version "< 5.1.0" + """ + id + name + identifier + description + status + is_public + inherit_members + created_on + updated_on + """ + + Scenario: Updating a project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I update the project with identifier "test-project" with the following data + | property | value | + | name | new project name | + | homepage | https://example.com | + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is exactly "" + + Scenario: Closing a project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I close the project with identifier "test-project" + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is true + When I show the project with identifier "test-project" + Then the returned data "project" property contains the following data + | property | value | + | status | 5 | + + Scenario: Reopening a project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I close the project with identifier "test-project" + When I reopen the project with identifier "test-project" + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is true + When I show the project with identifier "test-project" + Then the returned data "project" property contains the following data + | property | value | + | status | 1 | + + Scenario: Archiving a project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I archive the project with identifier "test-project" + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is true + + Scenario: Showing an archived project is not possible + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I archive the project with identifier "test-project" + When I show the project with identifier "test-project" + Then the response has the status code "403" + And the response has the content type "application/json" + And the response has the content "" + And the returned data is false + + Scenario: Unarchiving a project + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I archive the project with identifier "test-project" + When I unarchive the project with identifier "test-project" + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is true + When I show the project with identifier "test-project" + Then the returned data "project" property contains the following data + | property | value | + | status | 1 | diff --git a/tests/Behat/features/wiki.feature b/tests/Behat/features/wiki.feature new file mode 100644 index 00000000..08db58a8 --- /dev/null +++ b/tests/Behat/features/wiki.feature @@ -0,0 +1,231 @@ +Feature: Interacting with the REST API for wikis + In order to interact with REST API for wiki + As a user + I want to make sure the Redmine server replies with the correct response + + Scenario: Creating a wiki page + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + Then the response has the status code "201" + And the response has the content type "application/xml" + And the returned data is an instance of "SimpleXMLElement" + And the returned data has only the following properties + """ + title + text + version + author + comments + created_on + updated_on + """ + And the returned data has proterties with the following data + | property | value | + | title | Test+Page | + | text | # My first wiki page | + | version | 1 | + | comments | [] | + And the returned data "author" property is an array + And the returned data "author" property contains "1" items + And the returned data "author.@attributes" property is an array + And the returned data "author.@attributes" property has only the following properties + """ + id + name + """ + And the returned data "author.@attributes" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | + + Scenario: Creating a wiki page with attachment upload + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I upload the content of the file "%tests_dir%/Fixtures/testfile_01.txt" with the following data + | property | value | + | filename | testfile.txt | + When I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + | uploads | [{"token":"1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8","filename":"filename.txt","content-type":"text/plain"}] | + Then the response has the status code "201" + And the response has the content type "application/xml" + And the returned data is an instance of "SimpleXMLElement" + And the returned data has only the following properties + """ + title + text + version + author + comments + created_on + updated_on + """ + And the returned data has proterties with the following data + | property | value | + | title | Test+Page | + | text | # My first wiki page | + | version | 1 | + | comments | [] | + And the returned data "author" property is an array + And the returned data "author" property contains "1" items + And the returned data "author.@attributes" property is an array + And the returned data "author.@attributes" property has only the following properties + """ + id + name + """ + And the returned data "author.@attributes" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | + + Scenario: Showing a wiki page + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + When I show the wiki page with name "Test Page" and project identifier "test-project" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + wiki_page + """ + And the returned data "wiki_page" property is an array + And the returned data "wiki_page" property has only the following properties + """ + title + text + version + author + comments + created_on + updated_on + attachments + """ + And the returned data "wiki_page" property contains the following data + | property | value | + | title | Test+Page | + | text | # My first wiki page | + | version | 1 | + | comments | null | + | attachments | [] | + And the returned data "wiki_page.author" property is an array + And the returned data "wiki_page.author" property has only the following properties + """ + id + name + """ + And the returned data "wiki_page.author" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | + + Scenario: Showing a wiki page with uploaded attachment + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I upload the content of the file "%tests_dir%/Fixtures/testfile_01.txt" with the following data + | property | value | + | filename | testfile.txt | + And I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + | uploads | [{"token":"1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8","filename":"filename.txt","content-type":"text/plain"}] | + When I show the wiki page with name "Test Page" and project identifier "test-project" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data has only the following properties + """ + wiki_page + """ + And the returned data "wiki_page" property is an array + And the returned data "wiki_page" property has only the following properties + """ + title + text + version + author + comments + created_on + updated_on + attachments + """ + And the returned data "wiki_page" property contains the following data + | property | value | + | title | Test+Page | + | text | # My first wiki page | + | version | 1 | + | comments | null | + And the returned data "wiki_page.author" property is an array + And the returned data "wiki_page.author" property has only the following properties + """ + id + name + """ + And the returned data "wiki_page.author" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | + And the returned data "wiki_page.attachments" property is an array + And the returned data "wiki_page.attachments" property contains "1" items + And the returned data "wiki_page.attachments.0" property is an array + And the returned data "wiki_page.attachments.0" property has only the following properties + """ + id + filename + filesize + content_type + description + content_url + author + created_on + """ + And the returned data "wiki_page.attachments.0" property contains the following data + | property | value | + | id | 1 | + | filename | filename.txt | + | filesize | 65 | + | content_type | text/plain | + | description | | + | content_url | http://redmine-%redmine_id%:3000/attachments/download/1/filename.txt | + And the returned data "wiki_page.attachments.0.author" property is an array + And the returned data "wiki_page.attachments.0.author" property has only the following properties + """ + id + name + """ + And the returned data "wiki_page.attachments.0.author" property contains the following data + | property | value | + | id | 1 | + | name | Redmine Admin | + + Scenario: Updating a wiki page + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + When I update the wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # First Wiki page with changes | + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is exactly "" + + Scenario: Deleting a wiki page + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I create a wiki page with name "Test Page" and project identifier "test-project" with the following data + | property | value | + | text | # My first wiki page | + When I delete the wiki page with name "Test Page" and project identifier "test-project" + Then the response has the status code "204" + And the response has an empty content type + And the response has the content "" + And the returned data is exactly "" diff --git a/tests/End2End/Attachment/AttachmentTest.php b/tests/End2End/Attachment/AttachmentTest.php deleted file mode 100644 index 4ff1aad8..00000000 --- a/tests/End2End/Attachment/AttachmentTest.php +++ /dev/null @@ -1,68 +0,0 @@ -getNativeCurlClient($redmineVersion); - - // Create project - /** @var Project */ - $projectApi = $client->getApi('project'); - - $projectIdentifier = 'project-with-wiki'; - - $xmlData = $projectApi->create(['name' => 'project with wiki', 'identifier' => $projectIdentifier]); - - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); - - $this->assertIsArray($projectData, $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); - - // Upload file - /** @var Attachment */ - $attachmentApi = $client->getApi('attachment'); - - $jsonData = $attachmentApi->upload(file_get_contents(dirname(__FILE__, 3) . '/Fixtures/testfile_01.txt'), ['filename' => 'testfile.txt']); - - $attachmentData = json_decode($jsonData, true); - - $this->assertIsArray($attachmentData, $jsonData); - $this->assertArrayHasKey('upload', $attachmentData, $jsonData); - $this->assertSame( - ['id', 'token'], - array_keys($attachmentData['upload']), - $jsonData - ); - - $attachmentToken = $attachmentData['upload']['token']; - - $this->assertSame('1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8', $attachmentToken); - - // Check attachment - $attachmentData = $attachmentApi->show($attachmentData['upload']['id']); - - $jsonData = json_encode($attachmentData); - - $this->assertIsArray($attachmentData, $jsonData); - $this->assertArrayHasKey('attachment', $attachmentData, $jsonData); - $this->assertSame( - ['id', 'filename', 'filesize', 'content_type', 'description', 'content_url', 'author', 'created_on'], - array_keys($attachmentData['attachment']), - $jsonData - ); - } -} diff --git a/tests/End2End/ClientTestCase.php b/tests/End2End/ClientTestCase.php deleted file mode 100644 index d897f8b8..00000000 --- a/tests/End2End/ClientTestCase.php +++ /dev/null @@ -1,37 +0,0 @@ -> - */ - final public static function provideRedmineVersions(): array - { - $data = []; - - foreach (TestRunnerTracer::getSupportedRedmineVersions() as $version) { - $data[] = [$version]; - } - - return $data; - } - - protected function getNativeCurlClient(RedmineVersion $version): NativeCurlClient - { - $redmine = TestRunnerTracer::getRedmineInstance($version); - - return new NativeCurlClient( - $redmine->getRedmineUrl(), - $redmine->getApiKey() - ); - } -} diff --git a/tests/End2End/Group/GroupTest.php b/tests/End2End/Group/GroupTest.php deleted file mode 100644 index 2f033fc9..00000000 --- a/tests/End2End/Group/GroupTest.php +++ /dev/null @@ -1,80 +0,0 @@ -getNativeCurlClient($redmineVersion); - - /** @var Group */ - $groupApi = $client->getApi('group'); - $now = new DateTimeImmutable(); - - // Create group - $groupName = 'test group ' . $now->format('Y-m-d H:i:s'); - - $xmlData = $groupApi->create([ - 'name' => $groupName, - ]); - - $jsonData = json_encode($xmlData); - - $groupData = json_decode($jsonData, true); - - $this->assertIsArray($groupData, $jsonData); - $this->assertIsString($groupData['id'], $jsonData); - $this->assertSame($groupName, $groupData['name'], $jsonData); - - $groupId = (int) $groupData['id']; - - // List groups - $this->assertSame( - [ - 'groups' => [ - [ - 'id' => $groupId, - 'name' => $groupName, - ], - ], - ], - $groupApi->list() - ); - - // Read group - $this->assertSame( - [ - 'group' => [ - 'id' => $groupId, - 'name' => $groupName, - ] - ], - $groupApi->show($groupId) - ); - - // Update group - $result = $groupApi->update($groupId, ['name' => 'new group name']); - $this->assertSame('', $result); - - $this->assertSame( - [ - 'group' => [ - 'id' => $groupId, - 'name' => 'new group name', - ] - ], - $groupApi->show($groupId) - ); - } -} diff --git a/tests/End2End/Project/ArchivingProjectTest.php b/tests/End2End/Project/ArchivingProjectTest.php deleted file mode 100644 index 02763736..00000000 --- a/tests/End2End/Project/ArchivingProjectTest.php +++ /dev/null @@ -1,73 +0,0 @@ -getNativeCurlClient($redmineVersion); - - /** @var Project */ - $api = $client->getApi('project'); - - // Create project - $projectName = 'test project'; - $projectIdentifier = 'test_project'; - - $xmlData = $api->create([ - 'name' => $projectName, - 'identifier' => $projectIdentifier, - ]); - - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); - - $this->assertIsArray($projectData, $projectDataJson); - $this->assertArrayHasKey('identifier', $projectData, $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); - $this->assertArrayHasKey('status', $projectData, $projectDataJson); - $this->assertSame('1', $projectData['status'], $projectDataJson); - - // Archive project - $this->assertTrue($api->archive($projectIdentifier)); - - // Reading an archived project is not possible - $this->assertFalse($api->show($projectIdentifier)); - - // Unarchive project - $this->assertTrue($api->unarchive($projectIdentifier)); - - // Read single project - $projectDetails = $api->show($projectIdentifier); - - $this->assertArrayHasKey('project', $projectDetails); - $this->assertSame( - [ - 'id', - 'name', - 'identifier', - 'description', - 'homepage', - 'status', - 'is_public', - 'inherit_members', - 'trackers', - 'issue_categories', - 'created_on', - 'updated_on', - ], - array_keys($projectDetails['project']) - ); - $this->assertSame(1, $projectDetails['project']['status']); - } -} diff --git a/tests/End2End/Project/ClosingProjectTest.php b/tests/End2End/Project/ClosingProjectTest.php deleted file mode 100644 index 59213960..00000000 --- a/tests/End2End/Project/ClosingProjectTest.php +++ /dev/null @@ -1,93 +0,0 @@ -getNativeCurlClient($redmineVersion); - - /** @var Project */ - $api = $client->getApi('project'); - - // Create project - $projectName = 'test project'; - $projectIdentifier = 'test_project'; - - $xmlData = $api->create([ - 'name' => $projectName, - 'identifier' => $projectIdentifier, - ]); - - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); - - $this->assertIsArray($projectData, $projectDataJson); - $this->assertArrayHasKey('identifier', $projectData, $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); - $this->assertArrayHasKey('status', $projectData, $projectDataJson); - $this->assertSame('1', $projectData['status'], $projectDataJson); - - // Close project - $this->assertTrue($api->close($projectIdentifier)); - - // Read single project - $projectDetails = $api->show($projectIdentifier); - - $this->assertArrayHasKey('project', $projectDetails); - $this->assertSame( - [ - 'id', - 'name', - 'identifier', - 'description', - 'homepage', - 'status', - 'is_public', - 'inherit_members', - 'trackers', - 'issue_categories', - 'created_on', - 'updated_on', - ], - array_keys($projectDetails['project']) - ); - $this->assertSame(5, $projectDetails['project']['status']); - - // Reopen project - $this->assertTrue($api->reopen($projectIdentifier)); - - // Read single project - $projectDetails = $api->show($projectIdentifier); - - $this->assertArrayHasKey('project', $projectDetails); - $this->assertSame( - [ - 'id', - 'name', - 'identifier', - 'description', - 'homepage', - 'status', - 'is_public', - 'inherit_members', - 'trackers', - 'issue_categories', - 'created_on', - 'updated_on', - ], - array_keys($projectDetails['project']) - ); - $this->assertSame(1, $projectDetails['project']['status']); - } -} diff --git a/tests/End2End/Project/ProjectTest.php b/tests/End2End/Project/ProjectTest.php deleted file mode 100644 index 2cbb6b2e..00000000 --- a/tests/End2End/Project/ProjectTest.php +++ /dev/null @@ -1,135 +0,0 @@ -getNativeCurlClient($redmineVersion); - - /** @var Project */ - $api = $client->getApi('project'); - $now = new DateTimeImmutable(); - - // Create project - $projectName = 'test project ' . $now->format('Y-m-d H:i:s'); - $projectIdentifier = 'test_project_' . $now->format('Y-m-d_H-i-s'); - - $xmlData = $api->create([ - 'name' => $projectName, - 'identifier' => $projectIdentifier, - ]); - - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); - - $this->assertIsArray($projectData, $projectDataJson); - $this->assertIsString($projectData['id'], $projectDataJson); - $this->assertSame($projectName, $projectData['name'], $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); - - $projectId = (int) $projectData['id']; - - // List projects - $projectList = $api->list(); - - $this->assertSame( - [ - 'projects', - 'total_count', - 'offset', - 'limit', - ], - array_keys($projectList) - ); - - $expectedProject = [ - 'id' => $projectId, - 'name' => $projectName, - 'identifier' => $projectIdentifier, - 'description' => null, - 'homepage' => '', - 'status' => 1, - 'is_public' => true, - 'inherit_members' => false, - 'created_on' => $projectList['projects'][0]['created_on'], - 'updated_on' => $projectList['projects'][0]['updated_on'], - ]; - - // field 'homepage' was added in Redmine 5.1.0, see https://www.redmine.org/issues/39113 - if (version_compare($redmineVersion->asString(), '5.1.0', '<')) { - unset($expectedProject['homepage']); - } - - $this->assertSame( - [ - 'projects' => [ - $expectedProject, - ], - 'total_count' => 1, - 'offset' => 0, - 'limit' => 25, - ], - $projectList - ); - - // Update project - $result = $api->update($projectIdentifier, [ - 'name' => 'new project name', - 'homepage' => 'https://example.com', - ]); - $this->assertSame('', $result); - - // Read single project - $projectDetails = $api->show($projectIdentifier); - - $this->assertSame( - [ - 'id', - 'name', - 'identifier', - 'description', - 'homepage', - 'status', - 'is_public', - 'inherit_members', - 'trackers', - 'issue_categories', - 'created_on', - 'updated_on', - ], - array_keys($projectDetails['project']) - ); - - $this->assertSame( - [ - 'project' => [ - 'id' => $projectId, - 'name' => 'new project name', - 'identifier' => $projectIdentifier, - 'description' => null, - 'homepage' => 'https://example.com', - 'status' => 1, - 'is_public' => true, - 'inherit_members' => false, - 'trackers' => [], - 'issue_categories' => [], - 'created_on' => $projectDetails['project']['created_on'], - 'updated_on' => $projectDetails['project']['updated_on'], - ], - ], - $projectDetails, - ); - } -} diff --git a/tests/End2End/Wiki/WikiTest.php b/tests/End2End/Wiki/WikiTest.php deleted file mode 100644 index 8ec213a9..00000000 --- a/tests/End2End/Wiki/WikiTest.php +++ /dev/null @@ -1,100 +0,0 @@ -getNativeCurlClient($redmineVersion); - - // Create project - /** @var Project */ - $projectApi = $client->getApi('project'); - - $projectIdentifier = 'project-with-wiki'; - - $xmlData = $projectApi->create(['name' => 'project with wiki', 'identifier' => $projectIdentifier]); - - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); - - $this->assertIsArray($projectData, $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); - - // Upload file - /** @var Attachment */ - $attachmentApi = $client->getApi('attachment'); - - $jsonData = $attachmentApi->upload(file_get_contents(dirname(__FILE__, 3) . '/Fixtures/testfile_01.txt'), ['filename' => 'testfile.txt']); - - $attachmentData = json_decode($jsonData, true); - - $this->assertIsArray($attachmentData, $jsonData); - $this->assertArrayHasKey('upload', $attachmentData, $jsonData); - $this->assertSame( - ['id', 'token'], - array_keys($attachmentData['upload']), - $jsonData - ); - - $attachmentToken = $attachmentData['upload']['token']; - - $this->assertSame('1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8', $attachmentToken); - - // Add attachment to wiki page - /** @var Wiki */ - $wikiApi = $client->getApi('wiki'); - - $xmlData = $wikiApi->create($projectIdentifier, 'Test Page', [ - 'text' => '# First Wiki page', - 'uploads' => [ - ['token' => $attachmentToken, 'filename' => 'filename.txt', 'content-type' => 'text/plain'], - ], - ]); - - $wikiDataJson = json_encode($xmlData); - $wikiData = json_decode($wikiDataJson, true); - - $this->assertIsArray($wikiData, $wikiDataJson); - $this->assertSame( - ['title', 'text', 'version', 'author', 'comments', 'created_on', 'updated_on'], - array_keys($wikiData), - $wikiDataJson - ); - $this->assertSame('Test+Page', $wikiData['title'], $wikiDataJson); - - // Check attachments - $wikiData = $wikiApi->show($projectIdentifier, 'Test Page'); - - $this->assertIsArray($wikiData, json_encode($wikiData)); - $this->assertIsArray($wikiData['wiki_page']['attachments'][0]); - $this->assertSame( - ['id', 'filename', 'filesize', 'content_type', 'description', 'content_url', 'author', 'created_on'], - array_keys($wikiData['wiki_page']['attachments'][0]) - ); - - // Update wiki page returns empty string - $returnData = $wikiApi->update($projectIdentifier, 'Test Page', [ - 'text' => '# First Wiki page with changes', - ]); - - $this->assertSame('', $returnData, json_encode($returnData)); - - // Remove wiki page - $returnData = $wikiApi->remove($projectIdentifier, 'Test Page'); - - $this->assertSame('', $returnData, json_encode($returnData)); - } -} diff --git a/tests/RedmineExtension/TestRunnerTracer.php b/tests/RedmineExtension/BehatHookTracer.php similarity index 72% rename from tests/RedmineExtension/TestRunnerTracer.php rename to tests/RedmineExtension/BehatHookTracer.php index 8c83c757..177bdac5 100644 --- a/tests/RedmineExtension/TestRunnerTracer.php +++ b/tests/RedmineExtension/BehatHookTracer.php @@ -4,19 +4,18 @@ namespace Redmine\Tests\RedmineExtension; -use PHPUnit\Event\Event; -use PHPUnit\Event\Test\Finished as TestFinished; -use PHPUnit\Event\TestRunner\Finished as TestRunnerFinished; -use PHPUnit\Event\TestRunner\Started as TestRunnerStarted; -use PHPUnit\Event\Tracer\Tracer; +use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Behat\Testwork\Hook\Scope\AfterSuiteScope; +use Behat\Testwork\Hook\Scope\BeforeSuiteScope; +use Behat\Testwork\Hook\Scope\HookScope; use RuntimeException; -final class TestRunnerTracer implements Tracer +final class BehatHookTracer implements InstanceRegistration { /** * @var RedmineInstance[] $instances */ - private static ?TestRunnerTracer $tracer = null; + private static ?BehatHookTracer $tracer = null; /** * @var RedmineInstance[] $instances @@ -34,7 +33,7 @@ public static function getSupportedRedmineVersions(): array public static function getRedmineInstance(RedmineVersion $redmineVersion): RedmineInstance { if (static::$tracer === null) { - throw new RuntimeException('You can only get a Redmine instance while the PHPUnit Test Runner is running.'); + throw new RuntimeException('You can only get a Redmine instance while a Behat Suite is running.'); } if (! array_key_exists($redmineVersion->asId(), static::$instances)) { @@ -54,19 +53,19 @@ public function deregisterInstance(RedmineInstance $instance): void unset(static::$instances[$instance->getVersionId()]); } - public function trace(Event $event): void + public function hook(HookScope $event): void { - if ($event instanceof TestRunnerStarted) { + if ($event instanceof BeforeSuiteScope) { static::$tracer = $this; } - if ($event instanceof TestFinished) { + if ($event instanceof AfterScenarioScope) { foreach (static::$instances as $instance) { $instance->reset($this); } } - if ($event instanceof TestRunnerFinished) { + if ($event instanceof AfterSuiteScope) { foreach (static::$instances as $instance) { $instance->shutdown($this); } diff --git a/tests/RedmineExtension/InstanceRegistration.php b/tests/RedmineExtension/InstanceRegistration.php new file mode 100644 index 00000000..226eef6f --- /dev/null +++ b/tests/RedmineExtension/InstanceRegistration.php @@ -0,0 +1,12 @@ +registerTracer(new TestRunnerTracer()); - } -} diff --git a/tests/RedmineExtension/RedmineInstance.php b/tests/RedmineExtension/RedmineInstance.php index 58ceafd0..d469a583 100644 --- a/tests/RedmineExtension/RedmineInstance.php +++ b/tests/RedmineExtension/RedmineInstance.php @@ -22,9 +22,9 @@ public static function getSupportedVersions(): array } /** - * @param TestRunnerTracer $tracer Required to ensure that RedmineInstance is created while Test Runner is running + * @param InstanceRegistration $tracer Required to ensure that RedmineInstance is created while Test Runner is running */ - public static function create(TestRunnerTracer $tracer, RedmineVersion $version): void + public static function create(InstanceRegistration $tracer, RedmineVersion $version): void { if (! in_array($version, static::getSupportedVersions())) { throw new InvalidArgumentException('Redmine ' . $version->asString() . ' is not supported.'); @@ -33,7 +33,7 @@ public static function create(TestRunnerTracer $tracer, RedmineVersion $version) $tracer->registerInstance(new self($tracer, $version)); } - private TestRunnerTracer $tracer; + private InstanceRegistration $tracer; private RedmineVersion $version; @@ -55,7 +55,7 @@ public static function create(TestRunnerTracer $tracer, RedmineVersion $version) private string $apiKey; - private function __construct(TestRunnerTracer $tracer, RedmineVersion $version) + private function __construct(InstanceRegistration $tracer, RedmineVersion $version) { $this->tracer = $tracer; $this->version = $version; @@ -87,6 +87,11 @@ public function getVersionId(): int return $this->version->asId(); } + public function getVersionString(): string + { + return $this->version->asString(); + } + public function getRedmineUrl(): string { return $this->redmineUrl; @@ -97,7 +102,7 @@ public function getApiKey(): string return $this->apiKey; } - public function reset(TestRunnerTracer $tracer): void + public function reset(InstanceRegistration $tracer): void { if ($tracer !== $this->tracer) { throw new InvalidArgumentException(); @@ -107,7 +112,7 @@ public function reset(TestRunnerTracer $tracer): void $this->restoreFromMigratedFiles(); } - public function shutdown(TestRunnerTracer $tracer): void + public function shutdown(InstanceRegistration $tracer): void { if ($tracer !== $this->tracer) { throw new InvalidArgumentException();