From 74cf91fbefc1d59e7a6f84808ec55cf0832a2543 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 8 Feb 2024 13:30:28 +0100 Subject: [PATCH 01/40] Install Behat --- composer.json | 4 ++- tests/Behat/Bootstrap/FeatureContext.php | 35 ++++++++++++++++++++++++ tests/Behat/behat.yml | 8 ++++++ tests/Behat/features/test.feature | 9 ++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/Behat/Bootstrap/FeatureContext.php create mode 100644 tests/Behat/behat.yml create mode 100644 tests/Behat/features/test.feature diff --git a/composer.json b/composer.json index 91c2eb24..28e75642 100644 --- a/composer.json +++ b/composer.json @@ -33,7 +33,8 @@ "phpunit/phpunit": "^9 || ^10.5", "guzzlehttp/psr7": "^2", "php-mock/php-mock-phpunit": "^2.6", - "phpstan/phpstan": "^1.10" + "phpstan/phpstan": "^1.10", + "behat/behat": "^3.14" }, "autoload": { "psr-4": { @@ -46,6 +47,7 @@ } }, "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\"", diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php new file mode 100644 index 00000000..2e854cc2 --- /dev/null +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -0,0 +1,35 @@ +assertTrue(true); + } + + /** + * @When I run the tests + */ + public function iRunTheTests() + { + $this->assertTrue(true); + } + + /** + * @Then some testable outcome is achieved + */ + public function someTestableOutcomeIsAchieved() + { + $this->assertTrue(true); + } +} diff --git a/tests/Behat/behat.yml b/tests/Behat/behat.yml new file mode 100644 index 00000000..371f449f --- /dev/null +++ b/tests/Behat/behat.yml @@ -0,0 +1,8 @@ +default: + suites: + default: + paths: + - '%paths.base%/features' + contexts: + - Redmine\Tests\Behat\Bootstrap\FeatureContext: + name: Foobar diff --git a/tests/Behat/features/test.feature b/tests/Behat/features/test.feature new file mode 100644 index 00000000..1ad298da --- /dev/null +++ b/tests/Behat/features/test.feature @@ -0,0 +1,9 @@ +Feature: Behat tests should be executed + In order to write tests with behat + As a developer + I want to make shure behat works as intended + + Scenario: Calling the FeatureContext + Given an existing FeatureContext + When I run the tests + Then some testable outcome is achieved From f741fbe3e6922bae804602d3045ee507a7afc28a Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 8 Feb 2024 14:44:42 +0100 Subject: [PATCH 02/40] Add support for Behat in RedmineExtension --- tests/Behat/Bootstrap/FeatureContext.php | 60 ++++++++++++++- tests/RedmineExtension/BehatHookTracer.php | 76 +++++++++++++++++++ .../RedmineExtension/InstanceRegistration.php | 12 +++ tests/RedmineExtension/RedmineInstance.php | 12 +-- tests/RedmineExtension/TestRunnerTracer.php | 2 +- 5 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 tests/RedmineExtension/BehatHookTracer.php create mode 100644 tests/RedmineExtension/InstanceRegistration.php diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 2e854cc2..f2d95f9b 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -5,16 +5,74 @@ namespace Redmine\Tests\Behat\Bootstrap; use Behat\Behat\Context\Context; +use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Behat\Testwork\Hook\Scope\AfterSuiteScope; +use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use PHPUnit\Framework\TestCase; +use Redmine\Api\Project; +use Redmine\Client\NativeCurlClient; +use Redmine\Http\HttpClient; +use Redmine\Tests\RedmineExtension\BehatHookTracer; final class FeatureContext extends TestCase implements Context { + private static ?BehatHookTracer $tracer = null; + + private static NativeCurlClient $client; + + /** + * @BeforeSuite + */ + public static function prepare(BeforeSuiteScope $scope) + { + static::$tracer = new BehatHookTracer(); + static::$tracer->hook($scope); + + $versions = static::$tracer::getSupportedRedmineVersions(); + + $redmine = static::$tracer::getRedmineInstance(array_shift($versions)); + + static::$client = new NativeCurlClient( + $redmine->getRedmineUrl(), + $redmine->getApiKey() + ); + } + + /** + * @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; + } + /** * @Given an existing FeatureContext */ public function anExistingFeaturecontext() { - $this->assertTrue(true); + // Create project + /** @var Project */ + $projectApi = static::$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); } /** diff --git a/tests/RedmineExtension/BehatHookTracer.php b/tests/RedmineExtension/BehatHookTracer.php new file mode 100644 index 00000000..4d043b36 --- /dev/null +++ b/tests/RedmineExtension/BehatHookTracer.php @@ -0,0 +1,76 @@ +asId(), static::$instances)) { + RedmineInstance::create(static::$tracer, $redmineVersion); + } + + return static::$instances[$redmineVersion->asId()]; + } + + public function registerInstance(RedmineInstance $instance): void + { + static::$instances[$instance->getVersionId()] = $instance; + } + + public function deregisterInstance(RedmineInstance $instance): void + { + unset(static::$instances[$instance->getVersionId()]); + } + + public function hook(HookScope $event): void + { + if ($event instanceof BeforeSuiteScope) { + static::$tracer = $this; + } + + if ($event instanceof AfterScenarioScope) { + foreach (static::$instances as $instance) { + $instance->reset($this); + } + } + + if ($event instanceof AfterSuiteScope) { + foreach (static::$instances as $instance) { + $instance->shutdown($this); + } + + static::$tracer = null; + } + } +} 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 @@ +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; @@ -97,7 +97,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 +107,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(); diff --git a/tests/RedmineExtension/TestRunnerTracer.php b/tests/RedmineExtension/TestRunnerTracer.php index 8c83c757..95732573 100644 --- a/tests/RedmineExtension/TestRunnerTracer.php +++ b/tests/RedmineExtension/TestRunnerTracer.php @@ -11,7 +11,7 @@ use PHPUnit\Event\Tracer\Tracer; use RuntimeException; -final class TestRunnerTracer implements Tracer +final class TestRunnerTracer implements Tracer, InstanceRegistration { /** * @var RedmineInstance[] $instances From e091cbd63b1663ca33af4486b730213480dcb7ba Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 8 Feb 2024 16:22:49 +0100 Subject: [PATCH 03/40] Create simple test to create a project --- src/Redmine/Api/AbstractApi.php | 2 +- tests/Behat/Bootstrap/FeatureContext.php | 87 ++++++++++++++-------- tests/Behat/features/projects.feature | 11 +++ tests/Behat/features/test.feature | 9 --- tests/RedmineExtension/BehatHookTracer.php | 2 +- 5 files changed, 71 insertions(+), 40 deletions(-) create mode 100644 tests/Behat/features/projects.feature delete mode 100644 tests/Behat/features/test.feature 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/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index f2d95f9b..b5af8352 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -6,20 +6,22 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Behat\Behat\Tester\Exception\PendingException; use Behat\Testwork\Hook\Scope\AfterSuiteScope; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; +use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Redmine\Api\Project; +use Redmine\Client\Client; use Redmine\Client\NativeCurlClient; -use Redmine\Http\HttpClient; +use Redmine\Http\Response; use Redmine\Tests\RedmineExtension\BehatHookTracer; +use Redmine\Tests\RedmineExtension\RedmineInstance; final class FeatureContext extends TestCase implements Context { private static ?BehatHookTracer $tracer = null; - private static NativeCurlClient $client; - /** * @BeforeSuite */ @@ -27,15 +29,6 @@ public static function prepare(BeforeSuiteScope $scope) { static::$tracer = new BehatHookTracer(); static::$tracer->hook($scope); - - $versions = static::$tracer::getSupportedRedmineVersions(); - - $redmine = static::$tracer::getRedmineInstance(array_shift($versions)); - - static::$client = new NativeCurlClient( - $redmine->getRedmineUrl(), - $redmine->getApiKey() - ); } /** @@ -55,39 +48,75 @@ public static function clean(AfterSuiteScope $scope) static::$tracer = null; } + private RedmineInstance $redmine; + + private Client $client; + + private Response $lastResponse; + + private mixed $lastReturn; + /** - * @Given an existing FeatureContext + * @Given I have a Redmine server with version :versionString */ - public function anExistingFeaturecontext() + public function iHaveARedmineServerWithVersion(string $versionString) { - // Create project - /** @var Project */ - $projectApi = static::$client->getApi('project'); + $version = null; + + foreach (static::$tracer::getSupportedRedmineVersions() as $redmineVersion) { + if ($redmineVersion->asString() === $versionString) { + $version = $redmineVersion; + break; + } + } - $projectIdentifier = 'project-with-wiki'; + if ($version === null) { + throw new InvalidArgumentException('Redmine ' . $versionString . ' is not supported.'); + } - $xmlData = $projectApi->create(['name' => 'project with wiki', 'identifier' => $projectIdentifier]); + $this->redmine = static::$tracer::getRedmineInstance($version); + } - $projectDataJson = json_encode($xmlData); - $projectData = json_decode($projectDataJson, true); + /** + * @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() + ); + } + + /** + * @When I create a project with name :name and identifier :identifier + */ + public function iCreateAProjectWithNameAndIdentifier($name, $identifier) + { + /** @var Project */ + $projectApi = $this->client->getApi('project'); - $this->assertIsArray($projectData, $projectDataJson); - $this->assertSame($projectIdentifier, $projectData['identifier'], $projectDataJson); + $this->lastReturn = $projectApi->create(['name' => $name, 'identifier' => $identifier]); + $this->lastResponse = $projectApi->getLastResponse(); } /** - * @When I run the tests + * @Then the response has the status code :statusCode */ - public function iRunTheTests() + public function theResponseHasTheStatusCode(int $statusCode) { - $this->assertTrue(true); + $this->assertSame($statusCode, $this->lastResponse->getStatusCode()); } /** - * @Then some testable outcome is achieved + * @Then the response has the content type :contentType */ - public function someTestableOutcomeIsAchieved() + public function theResponseHasTheContentType(string $contentType) { - $this->assertTrue(true); + $this->assertStringStartsWith($contentType, $this->lastResponse->getContentType()); } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature new file mode 100644 index 00000000..1a9b7e3f --- /dev/null +++ b/tests/Behat/features/projects.feature @@ -0,0 +1,11 @@ +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 Redmine server with version "5.1.1" + And 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" diff --git a/tests/Behat/features/test.feature b/tests/Behat/features/test.feature deleted file mode 100644 index 1ad298da..00000000 --- a/tests/Behat/features/test.feature +++ /dev/null @@ -1,9 +0,0 @@ -Feature: Behat tests should be executed - In order to write tests with behat - As a developer - I want to make shure behat works as intended - - Scenario: Calling the FeatureContext - Given an existing FeatureContext - When I run the tests - Then some testable outcome is achieved diff --git a/tests/RedmineExtension/BehatHookTracer.php b/tests/RedmineExtension/BehatHookTracer.php index 4d043b36..177bdac5 100644 --- a/tests/RedmineExtension/BehatHookTracer.php +++ b/tests/RedmineExtension/BehatHookTracer.php @@ -33,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)) { From 47db9ee53aa617f50f87cf03c423ec0511528e35 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 9 Feb 2024 08:40:19 +0100 Subject: [PATCH 04/40] Add test for creating project with more data --- tests/Behat/Bootstrap/FeatureContext.php | 24 +++++++++++++++++++++++- tests/Behat/behat.yml | 4 ++-- tests/Behat/features/projects.feature | 9 +++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index b5af8352..a5f5c3b0 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -7,6 +7,7 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\AfterScenarioScope; use Behat\Behat\Tester\Exception\PendingException; +use Behat\Gherkin\Node\TableNode; use Behat\Testwork\Hook\Scope\AfterSuiteScope; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; use InvalidArgumentException; @@ -95,7 +96,7 @@ public function iHaveAClient($clientName) /** * @When I create a project with name :name and identifier :identifier */ - public function iCreateAProjectWithNameAndIdentifier($name, $identifier) + public function iCreateAProjectWithNameAndIdentifier(string $name, string $identifier) { /** @var Project */ $projectApi = $this->client->getApi('project'); @@ -104,6 +105,27 @@ public function iCreateAProjectWithNameAndIdentifier($name, $identifier) $this->lastResponse = $projectApi->getLastResponse(); } + /** + * @When I create a project with name :name, identifier :identifier and the following data + */ + public function iCreateAProjectWithNameIdentifierAndTheFollowingData(string $name, string $identifier, TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['key']] = $row['value']; + } + + $data['name'] = $name; + $data['identifier'] = $identifier; + + /** @var Project */ + $projectApi = $this->client->getApi('project'); + + $this->lastReturn = $projectApi->create($data); + $this->lastResponse = $projectApi->getLastResponse(); + } + /** * @Then the response has the status code :statusCode */ diff --git a/tests/Behat/behat.yml b/tests/Behat/behat.yml index 371f449f..692d1399 100644 --- a/tests/Behat/behat.yml +++ b/tests/Behat/behat.yml @@ -1,8 +1,8 @@ default: suites: - default: + api_features: paths: - '%paths.base%/features' contexts: - Redmine\Tests\Behat\Bootstrap\FeatureContext: - name: Foobar + name: Behat diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 1a9b7e3f..48e7d74a 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -9,3 +9,12 @@ Feature: Interacting with the REST API for projects 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" + + Scenario: Creating a project with multiple parameters + Given I have a Redmine server with version "5.1.1" + And I have a "NativeCurlClient" client + When I create a project with name "Test Project", identifier "test-project" and the following data + | key | value | + | description | project description | + Then the response has the status code "201" + And the response has the content type "application/xml" From 87f9b560ec23bd556e53d95c72315a4b41ea106d Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 9 Feb 2024 08:54:32 +0100 Subject: [PATCH 05/40] Move redmine version from freature into config, allow testing of multiple redmine versions --- tests/Behat/Bootstrap/FeatureContext.php | 19 ++++++------------- tests/Behat/behat.yml | 10 ++++++++-- tests/Behat/features/projects.feature | 6 ++---- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index a5f5c3b0..edcffa48 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -18,6 +18,7 @@ use Redmine\Http\Response; use Redmine\Tests\RedmineExtension\BehatHookTracer; use Redmine\Tests\RedmineExtension\RedmineInstance; +use Redmine\Tests\RedmineExtension\RedmineVersion; final class FeatureContext extends TestCase implements Context { @@ -57,25 +58,17 @@ public static function clean(AfterSuiteScope $scope) private mixed $lastReturn; - /** - * @Given I have a Redmine server with version :versionString - */ - public function iHaveARedmineServerWithVersion(string $versionString) + public function __construct(string $redmineVersion) { - $version = null; - - foreach (static::$tracer::getSupportedRedmineVersions() as $redmineVersion) { - if ($redmineVersion->asString() === $versionString) { - $version = $redmineVersion; - break; - } - } + $version = RedmineVersion::tryFrom($redmineVersion); if ($version === null) { - throw new InvalidArgumentException('Redmine ' . $versionString . ' is not supported.'); + throw new InvalidArgumentException('Redmine ' . $redmineVersion . ' is not supported.'); } $this->redmine = static::$tracer::getRedmineInstance($version); + + parent::__construct('BehatRedmine' . $version->asId()); } /** diff --git a/tests/Behat/behat.yml b/tests/Behat/behat.yml index 692d1399..cf006462 100644 --- a/tests/Behat/behat.yml +++ b/tests/Behat/behat.yml @@ -1,8 +1,14 @@ default: suites: - api_features: + redmine_50101: paths: - '%paths.base%/features' contexts: - Redmine\Tests\Behat\Bootstrap\FeatureContext: - name: Behat + 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/projects.feature b/tests/Behat/features/projects.feature index 48e7d74a..39cd2887 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -4,15 +4,13 @@ Feature: Interacting with the REST API for projects I want to make sure the Redmine server replies with the correct response Scenario: Creating a project with minimal parameters - Given I have a Redmine server with version "5.1.1" - And I have a "NativeCurlClient" client + 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" Scenario: Creating a project with multiple parameters - Given I have a Redmine server with version "5.1.1" - And I have a "NativeCurlClient" client + Given I have a "NativeCurlClient" client When I create a project with name "Test Project", identifier "test-project" and the following data | key | value | | description | project description | From 9d67fe8294937173bab22168baa0b8b81f0aa6f8 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 9 Feb 2024 10:42:30 +0100 Subject: [PATCH 06/40] Add test for properties of returned data --- tests/Behat/Bootstrap/FeatureContext.php | 26 ++++++++++++++++++++++ tests/Behat/features/projects.feature | 28 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index edcffa48..665899f8 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -7,6 +7,7 @@ use Behat\Behat\Context\Context; use Behat\Behat\Hook\Scope\AfterScenarioScope; use Behat\Behat\Tester\Exception\PendingException; +use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; use Behat\Testwork\Hook\Scope\AfterSuiteScope; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; @@ -19,6 +20,7 @@ use Redmine\Tests\RedmineExtension\BehatHookTracer; use Redmine\Tests\RedmineExtension\RedmineInstance; use Redmine\Tests\RedmineExtension\RedmineVersion; +use SimpleXMLElement; final class FeatureContext extends TestCase implements Context { @@ -134,4 +136,28 @@ public function theResponseHasTheContentType(string $contentType) { $this->assertStringStartsWith($contentType, $this->lastResponse->getContentType()); } + + /** + * @Then the returned data is an instance of :className + */ + public function theReturnedDataIsAnInstanceOf(string $className) + { + $this->assertInstanceOf($className, $this->lastReturn); + } + + /** + * @Then the returned data has the following properties + */ + public function theReturnedDataHasTheFollowingProperties(PyStringNode $string) + { + $properties = []; + + if ($this->lastReturn instanceof SimpleXMLElement) { + $properties = array_keys(get_object_vars($this->lastReturn)); + + $this->assertSame($string->getStrings(), $properties); + } else { + throw new PendingException(); + } + } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 39cd2887..088d2e4a 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -8,6 +8,20 @@ Feature: Interacting with the REST API for projects 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 the following properties + """ + id + name + identifier + description + homepage + status + is_public + inherit_members + created_on + updated_on + """ Scenario: Creating a project with multiple parameters Given I have a "NativeCurlClient" client @@ -16,3 +30,17 @@ Feature: Interacting with the REST API for projects | description | project description | 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 the following properties + """ + id + name + identifier + description + homepage + status + is_public + inherit_members + created_on + updated_on + """ From 1ee77c3100608fad62effa52cb21e64c39ec3fc0 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 9 Feb 2024 13:19:24 +0100 Subject: [PATCH 07/40] Test returned data after creating project --- tests/Behat/Bootstrap/FeatureContext.php | 54 ++++++++++++++++++------ tests/Behat/features/projects.feature | 38 +++++++++-------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 665899f8..e77987ae 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -11,6 +11,7 @@ use Behat\Gherkin\Node\TableNode; use Behat\Testwork\Hook\Scope\AfterSuiteScope; use Behat\Testwork\Hook\Scope\BeforeSuiteScope; +use Exception; use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Redmine\Api\Project; @@ -93,27 +94,26 @@ public function iHaveAClient($clientName) */ public function iCreateAProjectWithNameAndIdentifier(string $name, string $identifier) { - /** @var Project */ - $projectApi = $this->client->getApi('project'); + $table = new TableNode([ + ['property', 'value'], + ['name', $name], + ['identifier', $identifier], + ]); - $this->lastReturn = $projectApi->create(['name' => $name, 'identifier' => $identifier]); - $this->lastResponse = $projectApi->getLastResponse(); + $this->iCreateAProjectWithTheFollowingData($table); } /** - * @When I create a project with name :name, identifier :identifier and the following data + * @When I create a project with the following data */ - public function iCreateAProjectWithNameIdentifierAndTheFollowingData(string $name, string $identifier, TableNode $table) + public function iCreateAProjectWithTheFollowingData(TableNode $table) { $data = []; foreach ($table as $row) { - $data[$row['key']] = $row['value']; + $data[$row['property']] = $row['value']; } - $data['name'] = $name; - $data['identifier'] = $identifier; - /** @var Project */ $projectApi = $this->client->getApi('project'); @@ -146,9 +146,9 @@ public function theReturnedDataIsAnInstanceOf(string $className) } /** - * @Then the returned data has the following properties + * @Then the returned data has only the following properties */ - public function theReturnedDataHasTheFollowingProperties(PyStringNode $string) + public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $string) { $properties = []; @@ -160,4 +160,34 @@ public function theReturnedDataHasTheFollowingProperties(PyStringNode $string) throw new PendingException(); } } + + /** + * @Then the returned data has proterties with the following data + */ + public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $table) + { + if ($this->lastReturn instanceof SimpleXMLElement) { + $returnData = json_decode(json_encode($this->lastReturn), true); + } else { + throw new PendingException(); + } + + if (! is_array($returnData)) { + throw new Exception('Last return could not converted to array.'); + } + + foreach ($table as $row) { + $this->assertArrayHasKey($row['property'], $returnData); + + $value = $returnData[$row['property']]; + + if ($value instanceof SimpleXMLElement) { + $value = strval($value); + } + + $expected = $row['value']; + + $this->assertSame($expected, $value, 'Error with property ' . $row['property']); + } + } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 088d2e4a..7262164b 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -9,7 +9,7 @@ Feature: Interacting with the REST API for projects 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 the following properties + And the returned data has only the following properties """ id name @@ -22,25 +22,29 @@ Feature: Interacting with the REST API for projects 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 name "Test Project", identifier "test-project" and the following data - | key | value | - | description | project description | + 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 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 | + | description | project description | + | homepage | https://example.com | + | is_public | true | + | inherit_members | false | From fd74f2a2b5118eb43246eac63111be2a7dee1580 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 9 Feb 2024 16:18:27 +0100 Subject: [PATCH 08/40] Add scenario to list projects --- src/Redmine/Api/Project.php | 2 +- tests/Behat/Bootstrap/FeatureContext.php | 17 +++++++++++++++-- tests/Behat/features/projects.feature | 7 +++++++ 3 files changed, 23 insertions(+), 3 deletions(-) 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/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index e77987ae..5e42e5bd 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -121,12 +121,25 @@ public function iCreateAProjectWithTheFollowingData(TableNode $table) $this->lastResponse = $projectApi->getLastResponse(); } + /** + * @When I list all projects + */ + public function iListAllProjects() + { + /** @var Project */ + $projectApi = $this->client->getApi('project'); + + $this->lastReturn = $projectApi->list(); + $this->lastResponse = $projectApi->getLastResponse(); + var_dump($this->lastReturn); + } + /** * @Then the response has the status code :statusCode */ public function theResponseHasTheStatusCode(int $statusCode) { - $this->assertSame($statusCode, $this->lastResponse->getStatusCode()); + $this->assertSame($statusCode, $this->lastResponse->getStatusCode(), 'Raw response content: ' . $this->lastResponse->getContent()); } /** @@ -134,7 +147,7 @@ public function theResponseHasTheStatusCode(int $statusCode) */ public function theResponseHasTheContentType(string $contentType) { - $this->assertStringStartsWith($contentType, $this->lastResponse->getContentType()); + $this->assertStringStartsWith($contentType, $this->lastResponse->getContentType(), 'Raw response content: ' . $this->lastResponse->getContent()); } /** diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 7262164b..a879f72a 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -48,3 +48,10 @@ Feature: Interacting with the REST API for projects | homepage | https://example.com | | is_public | true | | inherit_members | false | + + Scenario: List of all projects + Given I have a "NativeCurlClient" client + When I create a project with name "Test Project" and identifier "test-project" + And I list all projects + Then the response has the status code "200" + And the response has the content type "application/json" From 843983b01e9dfaaabbf9ef22cea96e9cce0a7237 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 11:04:19 +0100 Subject: [PATCH 09/40] Add tests for listing empty projects list --- tests/Behat/Bootstrap/FeatureContext.php | 30 ++++++++++++++++++------ tests/Behat/features/projects.feature | 25 +++++++++++++++++++- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 5e42e5bd..f1acbc30 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -131,7 +131,6 @@ public function iListAllProjects() $this->lastReturn = $projectApi->list(); $this->lastResponse = $projectApi->getLastResponse(); - var_dump($this->lastReturn); } /** @@ -139,7 +138,11 @@ public function iListAllProjects() */ public function theResponseHasTheStatusCode(int $statusCode) { - $this->assertSame($statusCode, $this->lastResponse->getStatusCode(), 'Raw response content: ' . $this->lastResponse->getContent()); + $this->assertSame( + $statusCode, + $this->lastResponse->getStatusCode(), + 'Raw response content: ' . $this->lastResponse->getContent() + ); } /** @@ -147,7 +150,11 @@ public function theResponseHasTheStatusCode(int $statusCode) */ public function theResponseHasTheContentType(string $contentType) { - $this->assertStringStartsWith($contentType, $this->lastResponse->getContentType(), 'Raw response content: ' . $this->lastResponse->getContent()); + $this->assertStringStartsWith( + $contentType, + $this->lastResponse->getContentType(), + 'Raw response content: ' . $this->lastResponse->getContent() + ); } /** @@ -167,11 +174,13 @@ public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $strin if ($this->lastReturn instanceof SimpleXMLElement) { $properties = array_keys(get_object_vars($this->lastReturn)); - - $this->assertSame($string->getStrings(), $properties); + } else if (is_array($this->lastReturn)) { + $properties = array_keys($this->lastReturn); } else { - throw new PendingException(); + throw new PendingException(__METHOD__); } + + $this->assertSame($string->getStrings(), $properties); } /** @@ -181,8 +190,10 @@ public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $tabl { if ($this->lastReturn instanceof SimpleXMLElement) { $returnData = json_decode(json_encode($this->lastReturn), true); + } else if (is_array($this->lastReturn)) { + $returnData = $this->lastReturn; } else { - throw new PendingException(); + throw new PendingException(__METHOD__); } if (! is_array($returnData)) { @@ -200,6 +211,11 @@ public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $tabl $expected = $row['value']; + // Handle expected int values + if (is_int($value) && ctype_digit($expected)) { + $expected = intval($expected); + } + $this->assertSame($expected, $value, 'Error with property ' . $row['property']); } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index a879f72a..2df3688a 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -49,9 +49,32 @@ Feature: Interacting with the REST API for projects | is_public | true | | inherit_members | false | - Scenario: List of all projects + Scenario: List of all projects without any projects + Given I have a "NativeCurlClient" client + And 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 has proterties with the following data + | property | value | + | total_count | 0 | + | offset | 0 | + | limit | 25 | + + Scenario: List of all projects with one project Given I have a "NativeCurlClient" client When I create a project with name "Test Project" and identifier "test-project" And 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 | From dc8969ce14649ba9ec432b7337048c94bbb384b1 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 11:42:10 +0100 Subject: [PATCH 10/40] Add methods to check property types of returned data --- tests/Behat/Bootstrap/FeatureContext.php | 71 ++++++++++++++++++------ tests/Behat/features/projects.feature | 4 ++ 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index f1acbc30..e26f2a9f 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -21,6 +21,7 @@ use Redmine\Tests\RedmineExtension\BehatHookTracer; use Redmine\Tests\RedmineExtension\RedmineInstance; use Redmine\Tests\RedmineExtension\RedmineVersion; +use RuntimeException; use SimpleXMLElement; final class FeatureContext extends TestCase implements Context @@ -61,6 +62,8 @@ public static function clean(AfterSuiteScope $scope) private mixed $lastReturn; + private array $lastReturnAsArray; + public function __construct(string $redmineVersion) { $version = RedmineVersion::tryFrom($redmineVersion); @@ -117,6 +120,7 @@ public function iCreateAProjectWithTheFollowingData(TableNode $table) /** @var Project */ $projectApi = $this->client->getApi('project'); + unset($this->lastReturnAsArray); $this->lastReturn = $projectApi->create($data); $this->lastResponse = $projectApi->getLastResponse(); } @@ -129,6 +133,7 @@ public function iListAllProjects() /** @var Project */ $projectApi = $this->client->getApi('project'); + unset($this->lastReturnAsArray); $this->lastReturn = $projectApi->list(); $this->lastResponse = $projectApi->getLastResponse(); } @@ -170,15 +175,7 @@ public function theReturnedDataIsAnInstanceOf(string $className) */ public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $string) { - $properties = []; - - if ($this->lastReturn instanceof SimpleXMLElement) { - $properties = array_keys(get_object_vars($this->lastReturn)); - } else if (is_array($this->lastReturn)) { - $properties = array_keys($this->lastReturn); - } else { - throw new PendingException(__METHOD__); - } + $properties = array_keys($this->getLastReturnAsArray()); $this->assertSame($string->getStrings(), $properties); } @@ -188,13 +185,7 @@ public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $strin */ public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $table) { - if ($this->lastReturn instanceof SimpleXMLElement) { - $returnData = json_decode(json_encode($this->lastReturn), true); - } else if (is_array($this->lastReturn)) { - $returnData = $this->lastReturn; - } else { - throw new PendingException(__METHOD__); - } + $returnData = $this->getLastReturnAsArray(); if (! is_array($returnData)) { throw new Exception('Last return could not converted to array.'); @@ -219,4 +210,52 @@ public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $tabl $this->assertSame($expected, $value, 'Error with property ' . $row['property']); } } + + /** + * @Then the returned data :property property is an array + */ + public function theReturnedDataPropertyIsAnArray($property) + { + $returnData = $this->getLastReturnAsArray(); + + $value = $returnData[$property] ?? null; + + $this->assertIsArray($value); + } + + /** + * @Then the returned data :property property containts :count items + */ + public function theReturnedDataPropertyContaintsItems($property, int $count) + { + $returnData = $this->getLastReturnAsArray(); + + $value = $returnData[$property] ?? null; + + $this->assertCount($count, $value); + } + + private function getLastReturnAsArray(): array + { + if (isset($this->lastReturnAsArray)) { + return $this->lastReturnAsArray; + } + + if ($this->lastReturn instanceof SimpleXMLElement) { + $returnData = json_decode(json_encode($this->lastReturn), true); + } else if (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; + } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 2df3688a..9cd74fde 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -61,6 +61,8 @@ Feature: Interacting with the REST API for projects offset limit """ + And the returned data "projects" property is an array + And the returned data "projects" property containts "0" items And the returned data has proterties with the following data | property | value | | total_count | 0 | @@ -78,3 +80,5 @@ Feature: Interacting with the REST API for projects | total_count | 1 | | offset | 0 | | limit | 25 | + And the returned data "projects" property is an array + And the returned data "projects" property containts "1" items From 241f0745ce95f1ebf7d59bf4a543ee95fdcbb3c2 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 13:16:50 +0100 Subject: [PATCH 11/40] Allow checking properties based on redmine version --- tests/Behat/Bootstrap/FeatureContext.php | 58 ++++++++++++++++++++-- tests/Behat/features/projects.feature | 28 +++++++++++ tests/RedmineExtension/RedmineInstance.php | 5 ++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index e26f2a9f..9ddf5cb5 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -175,9 +175,7 @@ public function theReturnedDataIsAnInstanceOf(string $className) */ public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $string) { - $properties = array_keys($this->getLastReturnAsArray()); - - $this->assertSame($string->getStrings(), $properties); + $this->theReturnedDataPropertyHasOnlyTheFollowingProperties(null, $string); } /** @@ -218,7 +216,7 @@ public function theReturnedDataPropertyIsAnArray($property) { $returnData = $this->getLastReturnAsArray(); - $value = $returnData[$property] ?? null; + $value = $this->getItemFromArray($returnData, $property); $this->assertIsArray($value); } @@ -230,11 +228,41 @@ public function theReturnedDataPropertyContaintsItems($property, int $count) { $returnData = $this->getLastReturnAsArray(); - $value = $returnData[$property] ?? null; + $value = $this->getItemFromArray($returnData, $property); $this->assertCount($count, $value); } + /** + * @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)) { @@ -258,4 +286,24 @@ private function getLastReturnAsArray(): array 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/features/projects.feature b/tests/Behat/features/projects.feature index 9cd74fde..707178b2 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -69,6 +69,7 @@ Feature: Interacting with the REST API for projects | offset | 0 | | limit | 25 | + @wip Scenario: List of all projects with one project Given I have a "NativeCurlClient" client When I create a project with name "Test Project" and identifier "test-project" @@ -82,3 +83,30 @@ Feature: Interacting with the REST API for projects | limit | 25 | And the returned data "projects" property is an array And the returned data "projects" property containts "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 + """ diff --git a/tests/RedmineExtension/RedmineInstance.php b/tests/RedmineExtension/RedmineInstance.php index b2cdf321..d469a583 100644 --- a/tests/RedmineExtension/RedmineInstance.php +++ b/tests/RedmineExtension/RedmineInstance.php @@ -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; From 9bbabbfa1f6e9cccde20c7590fedb7642aba936d Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 13:46:15 +0100 Subject: [PATCH 12/40] Add scenatio updating a project --- tests/Behat/Bootstrap/FeatureContext.php | 63 ++++++--------- tests/Behat/Bootstrap/ProjectContextTrait.php | 80 +++++++++++++++++++ tests/Behat/features/projects.feature | 16 +++- 3 files changed, 116 insertions(+), 43 deletions(-) create mode 100644 tests/Behat/Bootstrap/ProjectContextTrait.php diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 9ddf5cb5..a164da95 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -14,7 +14,6 @@ use Exception; use InvalidArgumentException; use PHPUnit\Framework\TestCase; -use Redmine\Api\Project; use Redmine\Client\Client; use Redmine\Client\NativeCurlClient; use Redmine\Http\Response; @@ -26,6 +25,8 @@ final class FeatureContext extends TestCase implements Context { + use ProjectContextTrait; + private static ?BehatHookTracer $tracer = null; /** @@ -92,50 +93,16 @@ public function iHaveAClient($clientName) ); } - /** - * @When I create a project with name :name and identifier :identifier - */ - public function iCreateAProjectWithNameAndIdentifier(string $name, string $identifier) - { - $table = new TableNode([ - ['property', 'value'], - ['name', $name], - ['identifier', $identifier], - ]); - - $this->iCreateAProjectWithTheFollowingData($table); - } - - /** - * @When I create a project with the following data - */ - public function iCreateAProjectWithTheFollowingData(TableNode $table) + private function getNativeCurlClient(): NativeCurlClient { - $data = []; - - foreach ($table as $row) { - $data[$row['property']] = $row['value']; - } - - /** @var Project */ - $projectApi = $this->client->getApi('project'); - - unset($this->lastReturnAsArray); - $this->lastReturn = $projectApi->create($data); - $this->lastResponse = $projectApi->getLastResponse(); + return $this->client; } - /** - * @When I list all projects - */ - public function iListAllProjects() + private function registerClientResponse(mixed $lastReturn, Response $lastResponse): void { - /** @var Project */ - $projectApi = $this->client->getApi('project'); - unset($this->lastReturnAsArray); - $this->lastReturn = $projectApi->list(); - $this->lastResponse = $projectApi->getLastResponse(); + $this->lastReturn = $lastReturn; + $this->lastResponse = $lastResponse; } /** @@ -162,6 +129,22 @@ public function theResponseHasTheContentType(string $contentType) ); } + /** + * @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 an instance of :className */ diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php new file mode 100644 index 00000000..659e1448 --- /dev/null +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -0,0 +1,80 @@ +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 */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->create($data), + $projectApi->getLastResponse() + ); + } + + /** + * @When I list all projects + */ + public function iListAllProjects() + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->list(), + $projectApi->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 */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->update($identifier, $data), + $projectApi->getLastResponse() + ); + } +} diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 707178b2..02a9a6e6 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -49,7 +49,7 @@ Feature: Interacting with the REST API for projects | is_public | true | | inherit_members | false | - Scenario: List of all projects without any projects + Scenario: Listing of zero projects Given I have a "NativeCurlClient" client And I list all projects Then the response has the status code "200" @@ -69,8 +69,7 @@ Feature: Interacting with the REST API for projects | offset | 0 | | limit | 25 | - @wip - Scenario: List of all projects with one project + Scenario: Listing of one project Given I have a "NativeCurlClient" client When I create a project with name "Test Project" and identifier "test-project" And I list all projects @@ -110,3 +109,14 @@ Feature: Interacting with the REST API for projects created_on updated_on """ + + Scenario: Updating a project + Given I have a "NativeCurlClient" client + When I create a project with name "Test Project" and identifier "test-project" + And 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 "" From f31107203d8ff514681445cba91215cef64cdf7f Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 14:02:21 +0100 Subject: [PATCH 13/40] Add scenario to show a project --- tests/Behat/Bootstrap/ProjectContextTrait.php | 14 +++++++++ tests/Behat/features/projects.feature | 29 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php index 659e1448..04db8b5f 100644 --- a/tests/Behat/Bootstrap/ProjectContextTrait.php +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -58,6 +58,20 @@ public function iListAllProjects() ); } + /** + * @When I show the project with identifier :identifier + */ + public function iShowTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->show($identifier), + $projectApi->getLastResponse() + ); + } + /** * @When I update the project with identifier :identifier with the following data */ diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 02a9a6e6..f015145d 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -49,9 +49,36 @@ Feature: Interacting with the REST API for projects | is_public | true | | inherit_members | false | + Scenario: Showing a specific project + Given I have a "NativeCurlClient" client + When I create a project with name "Test Project" and identifier "test-project" + And 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 + """ + Scenario: Listing of zero projects Given I have a "NativeCurlClient" client - And I list all projects + 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 From 53d14cd15bb0cd2e61b47f6eb499247f452ab61a Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 14:37:18 +0100 Subject: [PATCH 14/40] Add tests for properties showing a project --- tests/Behat/Bootstrap/FeatureContext.php | 72 ++++++++++++++++-------- tests/Behat/features/projects.feature | 12 ++++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index a164da95..bd52f648 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -166,30 +166,7 @@ public function theReturnedDataHasOnlyTheFollowingProperties(PyStringNode $strin */ public function theReturnedDataHasProtertiesWithTheFollowingData(TableNode $table) { - $returnData = $this->getLastReturnAsArray(); - - if (! is_array($returnData)) { - throw new Exception('Last return could not converted to 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 int values - if (is_int($value) && ctype_digit($expected)) { - $expected = intval($expected); - } - - $this->assertSame($expected, $value, 'Error with property ' . $row['property']); - } + $this->theReturnedDataPropertyContainsTheFollowingData(null, $table); } /** @@ -216,6 +193,53 @@ public function theReturnedDataPropertyContaintsItems($property, int $count) $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); + + 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; + } + + $this->assertSame($expected, $value, 'Error with property "' . $row['property'] . '"'); + } + } + /** * @Then the returned data :property property has only the following properties */ diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index f015145d..a80a466e 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -75,6 +75,18 @@ Feature: Interacting with the REST API for projects 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 From 763ef6e1d099ff891bcb1bda235d947a0455a961 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 15:10:31 +0100 Subject: [PATCH 15/40] Add scenario to test closing and reopening a project --- tests/Behat/Bootstrap/ProjectContextTrait.php | 28 +++++++++++++++++++ tests/Behat/features/projects.feature | 25 +++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php index 04db8b5f..df8d6799 100644 --- a/tests/Behat/Bootstrap/ProjectContextTrait.php +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -91,4 +91,32 @@ public function iUpdateTheProjectWithIdentifierWithTheFollowingData(string $iden $projectApi->getLastResponse() ); } + + /** + * @When I close the project with identifier :identifier + */ + public function iCloseTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->close($identifier), + $projectApi->getLastResponse() + ); + } + + /** + * @When I reopen the project with identifier :identifier + */ + public function iReopenTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->reopen($identifier), + $projectApi->getLastResponse() + ); + } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index a80a466e..07a79cce 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -159,3 +159,28 @@ Feature: Interacting with the REST API for projects Then the response has the status code "204" And the response has an empty content type And the response has the content "" + + 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 "" + 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 "" + When I show the project with identifier "test-project" + Then the returned data "project" property contains the following data + | property | value | + | status | 1 | From 76f9a082168e7e36de0e5eb20567fb51accdc7e7 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 15:29:54 +0100 Subject: [PATCH 16/40] Add scenarios for archiving and unarchiving a project --- tests/Behat/Bootstrap/FeatureContext.php | 10 +++++ tests/Behat/Bootstrap/ProjectContextTrait.php | 28 ++++++++++++ tests/Behat/features/projects.feature | 43 ++++++++++++++++--- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index bd52f648..0a6c9642 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -153,6 +153,14 @@ public function theReturnedDataIsAnInstanceOf(string $className) $this->assertInstanceOf($className, $this->lastReturn); } + /** + * @Then the returned data is false + */ + public function theReturnedDataIsFalse() + { + $this->assertFalse($this->lastReturn); + } + /** * @Then the returned data has only the following properties */ @@ -276,6 +284,8 @@ private function getLastReturnAsArray(): array return $this->lastReturnAsArray; } + $returnData = null; + if ($this->lastReturn instanceof SimpleXMLElement) { $returnData = json_decode(json_encode($this->lastReturn), true); } else if (is_array($this->lastReturn)) { diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php index df8d6799..c714ca05 100644 --- a/tests/Behat/Bootstrap/ProjectContextTrait.php +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -119,4 +119,32 @@ public function iReopenTheProjectWithIdentifier(string $identifier) $projectApi->getLastResponse() ); } + + /** + * @When I archive the project with identifier :identifier + */ + public function iArchiveTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->archive($identifier), + $projectApi->getLastResponse() + ); + } + + /** + * @When I unarchive the project with identifier :identifier + */ + public function iUnarchiveTheProjectWithIdentifier(string $identifier) + { + /** @var Project */ + $projectApi = $this->getNativeCurlClient()->getApi('project'); + + $this->registerClientResponse( + $projectApi->unarchive($identifier), + $projectApi->getLastResponse() + ); + } } diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 07a79cce..7c9a8411 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -51,8 +51,8 @@ Feature: Interacting with the REST API for projects Scenario: Showing a specific project Given I have a "NativeCurlClient" client - When I create a project with name "Test Project" and identifier "test-project" - And I show the project with identifier "test-project" + 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 @@ -110,8 +110,8 @@ Feature: Interacting with the REST API for projects Scenario: Listing of one project Given I have a "NativeCurlClient" client - When I create a project with name "Test Project" and identifier "test-project" - And I list all projects + 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 @@ -151,8 +151,8 @@ Feature: Interacting with the REST API for projects Scenario: Updating a project Given I have a "NativeCurlClient" client - When I create a project with name "Test Project" and identifier "test-project" - And I update the project with identifier "test-project" with the following data + 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 | @@ -184,3 +184,34 @@ Feature: Interacting with the REST API for projects 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 "" + + 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 "" + When I show the project with identifier "test-project" + Then the returned data "project" property contains the following data + | property | value | + | status | 1 | From 712b1401c4c11f88d279534811998eca13b5326d Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 15:39:38 +0100 Subject: [PATCH 17/40] Remove migrated e2e tests --- .../End2End/Project/ArchivingProjectTest.php | 73 ---------- tests/End2End/Project/ClosingProjectTest.php | 93 ------------ tests/End2End/Project/ProjectTest.php | 135 ------------------ 3 files changed, 301 deletions(-) delete mode 100644 tests/End2End/Project/ArchivingProjectTest.php delete mode 100644 tests/End2End/Project/ClosingProjectTest.php delete mode 100644 tests/End2End/Project/ProjectTest.php 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, - ); - } -} From a038aff5023d1573f8df175c9ee7f3f06f1ddfd5 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 16:15:32 +0100 Subject: [PATCH 18/40] Add tests for returned data --- tests/Behat/Bootstrap/FeatureContext.php | 22 +++++++++++++++++++--- tests/Behat/features/projects.feature | 5 +++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 0a6c9642..ca21cb20 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -146,11 +146,11 @@ public function theResponseHasTheContent(string $content) } /** - * @Then the returned data is an instance of :className + * @Then the returned data is true */ - public function theReturnedDataIsAnInstanceOf(string $className) + public function theReturnedDataIsTrue() { - $this->assertInstanceOf($className, $this->lastReturn); + $this->assertTrue($this->lastReturn); } /** @@ -161,6 +161,22 @@ 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 */ diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 7c9a8411..6cc1ba58 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -159,6 +159,7 @@ Feature: Interacting with the REST API for projects 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 @@ -167,6 +168,7 @@ Feature: Interacting with the REST API for projects 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 | @@ -180,6 +182,7 @@ Feature: Interacting with the REST API for projects 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 | @@ -192,6 +195,7 @@ Feature: Interacting with the REST API for projects 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 @@ -211,6 +215,7 @@ Feature: Interacting with the REST API for projects 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 | From 1fea118f1806b31f064f60d4b741cd06f57887c4 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 16:25:55 +0100 Subject: [PATCH 19/40] add scenario for creating a group --- tests/Behat/Bootstrap/FeatureContext.php | 1 + tests/Behat/Bootstrap/GroupContextTrait.php | 30 +++++++++++++++++++++ tests/Behat/features/groups.feature | 20 ++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 tests/Behat/Bootstrap/GroupContextTrait.php create mode 100644 tests/Behat/features/groups.feature diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index ca21cb20..53ba9ce5 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -25,6 +25,7 @@ final class FeatureContext extends TestCase implements Context { + use GroupContextTrait; use ProjectContextTrait; private static ?BehatHookTracer $tracer = null; diff --git a/tests/Behat/Bootstrap/GroupContextTrait.php b/tests/Behat/Bootstrap/GroupContextTrait.php new file mode 100644 index 00000000..8dac1619 --- /dev/null +++ b/tests/Behat/Bootstrap/GroupContextTrait.php @@ -0,0 +1,30 @@ + $groupName, + ]; + + /** @var Group */ + $groupApi = $this->getNativeCurlClient()->getApi('group'); + + $this->registerClientResponse( + $groupApi->create($data), + $groupApi->getLastResponse() + ); + } +} diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature new file mode 100644 index 00000000..07b22ee1 --- /dev/null +++ b/tests/Behat/features/groups.feature @@ -0,0 +1,20 @@ +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 | From e29ec677cc16b6c0ecdd975e4dbb081b16776025 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 18:49:26 +0100 Subject: [PATCH 20/40] Set user for redmine services to 1000:1000 --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) 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: From b0960c5ec5634da3b6584e54c7b5ef5c6869e213 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 19:03:12 +0100 Subject: [PATCH 21/40] Refactor project tests --- tests/Behat/Bootstrap/ProjectContextTrait.php | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/Behat/Bootstrap/ProjectContextTrait.php b/tests/Behat/Bootstrap/ProjectContextTrait.php index c714ca05..ad037bdd 100644 --- a/tests/Behat/Bootstrap/ProjectContextTrait.php +++ b/tests/Behat/Bootstrap/ProjectContextTrait.php @@ -36,11 +36,11 @@ public function iCreateAProjectWithTheFollowingData(TableNode $table) } /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->create($data), - $projectApi->getLastResponse() + $api->create($data), + $api->getLastResponse() ); } @@ -50,11 +50,11 @@ public function iCreateAProjectWithTheFollowingData(TableNode $table) public function iListAllProjects() { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->list(), - $projectApi->getLastResponse() + $api->list(), + $api->getLastResponse() ); } @@ -64,11 +64,11 @@ public function iListAllProjects() public function iShowTheProjectWithIdentifier(string $identifier) { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->show($identifier), - $projectApi->getLastResponse() + $api->show($identifier), + $api->getLastResponse() ); } @@ -84,11 +84,11 @@ public function iUpdateTheProjectWithIdentifierWithTheFollowingData(string $iden } /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->update($identifier, $data), - $projectApi->getLastResponse() + $api->update($identifier, $data), + $api->getLastResponse() ); } @@ -98,11 +98,11 @@ public function iUpdateTheProjectWithIdentifierWithTheFollowingData(string $iden public function iCloseTheProjectWithIdentifier(string $identifier) { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->close($identifier), - $projectApi->getLastResponse() + $api->close($identifier), + $api->getLastResponse() ); } @@ -112,11 +112,11 @@ public function iCloseTheProjectWithIdentifier(string $identifier) public function iReopenTheProjectWithIdentifier(string $identifier) { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->reopen($identifier), - $projectApi->getLastResponse() + $api->reopen($identifier), + $api->getLastResponse() ); } @@ -126,11 +126,11 @@ public function iReopenTheProjectWithIdentifier(string $identifier) public function iArchiveTheProjectWithIdentifier(string $identifier) { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->archive($identifier), - $projectApi->getLastResponse() + $api->archive($identifier), + $api->getLastResponse() ); } @@ -140,11 +140,11 @@ public function iArchiveTheProjectWithIdentifier(string $identifier) public function iUnarchiveTheProjectWithIdentifier(string $identifier) { /** @var Project */ - $projectApi = $this->getNativeCurlClient()->getApi('project'); + $api = $this->getNativeCurlClient()->getApi('project'); $this->registerClientResponse( - $projectApi->unarchive($identifier), - $projectApi->getLastResponse() + $api->unarchive($identifier), + $api->getLastResponse() ); } } From 3443a036de8a3453bcd6b76bdffde028d4088740 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 19:03:31 +0100 Subject: [PATCH 22/40] Create tests for groups --- tests/Behat/Bootstrap/GroupContextTrait.php | 20 +++++++++++++++++--- tests/Behat/features/groups.feature | 12 ++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/Behat/Bootstrap/GroupContextTrait.php b/tests/Behat/Bootstrap/GroupContextTrait.php index 8dac1619..1d2c6d4a 100644 --- a/tests/Behat/Bootstrap/GroupContextTrait.php +++ b/tests/Behat/Bootstrap/GroupContextTrait.php @@ -20,11 +20,25 @@ public function iCreateAGroupWithName(string $groupName) ]; /** @var Group */ - $groupApi = $this->getNativeCurlClient()->getApi('group'); + $api = $this->getNativeCurlClient()->getApi('group'); $this->registerClientResponse( - $groupApi->create($data), - $groupApi->getLastResponse() + $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() ); } } diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature index 07b22ee1..69dda0b9 100644 --- a/tests/Behat/features/groups.feature +++ b/tests/Behat/features/groups.feature @@ -18,3 +18,15 @@ Feature: Interacting with the REST API for groups | 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 containts "0" items From ecec44a6efc03b35ece5b48fcba3ad9da835c1a0 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 21:13:14 +0100 Subject: [PATCH 23/40] Create tests for listing groups --- tests/Behat/features/groups.feature | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature index 69dda0b9..b82b1aeb 100644 --- a/tests/Behat/features/groups.feature +++ b/tests/Behat/features/groups.feature @@ -30,3 +30,26 @@ Feature: Interacting with the REST API for groups """ And the returned data "groups" property is an array And the returned data "groups" property containts "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 containts "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 | From 2f5e733d0571f27ca5b1846b2659b65cc8cf2a2d Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 21:30:22 +0100 Subject: [PATCH 24/40] Add tests for showing a group --- tests/Behat/Bootstrap/FeatureContext.php | 4 ++++ tests/Behat/Bootstrap/GroupContextTrait.php | 14 +++++++++++++ tests/Behat/features/groups.feature | 22 +++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 53ba9ce5..c62f639c 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -225,6 +225,10 @@ public function theReturnedDataPropertyContainsTheFollowingData($property, 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); diff --git a/tests/Behat/Bootstrap/GroupContextTrait.php b/tests/Behat/Bootstrap/GroupContextTrait.php index 1d2c6d4a..5f9ec9bd 100644 --- a/tests/Behat/Bootstrap/GroupContextTrait.php +++ b/tests/Behat/Bootstrap/GroupContextTrait.php @@ -41,4 +41,18 @@ public function iListAllGroups() $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() + ); + } } diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature index b82b1aeb..7a15e88a 100644 --- a/tests/Behat/features/groups.feature +++ b/tests/Behat/features/groups.feature @@ -53,3 +53,25 @@ Feature: Interacting with the REST API for groups | property | value | | id | 4 | | name | Test Group | + + @wip + 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 | \ No newline at end of file From 8f844ddbab6539f0270c80e281eb2f8390769102 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 21:43:28 +0100 Subject: [PATCH 25/40] Add tests to update a group --- tests/Behat/Bootstrap/GroupContextTrait.php | 41 +++++++++++++++++++-- tests/Behat/features/groups.feature | 16 +++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/tests/Behat/Bootstrap/GroupContextTrait.php b/tests/Behat/Bootstrap/GroupContextTrait.php index 5f9ec9bd..a4110626 100644 --- a/tests/Behat/Bootstrap/GroupContextTrait.php +++ b/tests/Behat/Bootstrap/GroupContextTrait.php @@ -15,9 +15,24 @@ trait GroupContextTrait */ public function iCreateAGroupWithName(string $groupName) { - $data = [ - 'name' => $groupName, - ]; + $table = new TableNode([ + ['property', 'value'], + ['name', $groupName], + ]); + + $this->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'); @@ -55,4 +70,24 @@ public function iShowTheGroupWithId(int $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/features/groups.feature b/tests/Behat/features/groups.feature index 7a15e88a..03cdd00e 100644 --- a/tests/Behat/features/groups.feature +++ b/tests/Behat/features/groups.feature @@ -54,7 +54,6 @@ Feature: Interacting with the REST API for groups | id | 4 | | name | Test Group | - @wip Scenario: Showing a specific group Given I have a "NativeCurlClient" client And I create a group with name "Test Group" @@ -74,4 +73,17 @@ Feature: Interacting with the REST API for groups And the returned data "group" property contains the following data | property | value | | id | 4 | - | name | Test Group | \ No newline at end of file + | 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 "" From cd27c852e8d662ad5c5d9649cf09286eb0667e62 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 21:46:23 +0100 Subject: [PATCH 26/40] Fix phpstan and code style --- tests/Behat/Bootstrap/FeatureContext.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index c62f639c..385ad1e3 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -58,7 +58,7 @@ public static function clean(AfterSuiteScope $scope) private RedmineInstance $redmine; - private Client $client; + private NativeCurlClient $client; private Response $lastResponse; @@ -309,7 +309,7 @@ private function getLastReturnAsArray(): array if ($this->lastReturn instanceof SimpleXMLElement) { $returnData = json_decode(json_encode($this->lastReturn), true); - } else if (is_array($this->lastReturn)) { + } elseif (is_array($this->lastReturn)) { $returnData = $this->lastReturn; } From 90e1f25482e00a74b6f46f34b1d8e1bfd9498498 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 21:48:18 +0100 Subject: [PATCH 27/40] remove e2e tests for groups --- tests/End2End/Group/GroupTest.php | 80 ------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 tests/End2End/Group/GroupTest.php 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) - ); - } -} From a1b71571604f9b3941cd70e2a193861c302471ff Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 22:20:09 +0100 Subject: [PATCH 28/40] create tests for uploading files --- .../Bootstrap/AttachmentContextTrait.php | 34 +++++++++++++++++++ tests/Behat/Bootstrap/FeatureContext.php | 3 ++ tests/Behat/features/attachments.feature | 27 +++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 tests/Behat/Bootstrap/AttachmentContextTrait.php create mode 100644 tests/Behat/features/attachments.feature diff --git a/tests/Behat/Bootstrap/AttachmentContextTrait.php b/tests/Behat/Bootstrap/AttachmentContextTrait.php new file mode 100644 index 00000000..cfa6c3ac --- /dev/null +++ b/tests/Behat/Bootstrap/AttachmentContextTrait.php @@ -0,0 +1,34 @@ +getNativeCurlClient()->getApi('attachment'); + + $this->registerClientResponse( + $api->upload(file_get_contents($filepath), $data), + $api->getLastResponse() + ); + } +} diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index 385ad1e3..b59081bf 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -25,6 +25,7 @@ final class FeatureContext extends TestCase implements Context { + use AttachmentContextTrait; use GroupContextTrait; use ProjectContextTrait; @@ -309,6 +310,8 @@ private function getLastReturnAsArray(): array 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; } diff --git a/tests/Behat/features/attachments.feature b/tests/Behat/features/attachments.feature new file mode 100644 index 00000000..8e2f3a4d --- /dev/null +++ b/tests/Behat/features/attachments.feature @@ -0,0 +1,27 @@ +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 + And I create a project with name "Test Project" and identifier "test-project" + 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 | From c2232cddbef7c3a281cdd22daf3ea7abb5f7f44e Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 22:38:41 +0100 Subject: [PATCH 29/40] Add tests showing attachment details --- .../Bootstrap/AttachmentContextTrait.php | 14 +++++++ tests/Behat/Bootstrap/FeatureContext.php | 5 +++ tests/Behat/features/attachments.feature | 40 ++++++++++++++++++- 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/Behat/Bootstrap/AttachmentContextTrait.php b/tests/Behat/Bootstrap/AttachmentContextTrait.php index cfa6c3ac..7b234891 100644 --- a/tests/Behat/Bootstrap/AttachmentContextTrait.php +++ b/tests/Behat/Bootstrap/AttachmentContextTrait.php @@ -31,4 +31,18 @@ public function iUploadTheContentOfTheFileWithTheFollowingData(string $filepath, $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 index b59081bf..a853099d 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -266,6 +266,11 @@ public function theReturnedDataPropertyContainsTheFollowingData($property, Table $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'] . '"'); } } diff --git a/tests/Behat/features/attachments.feature b/tests/Behat/features/attachments.feature index 8e2f3a4d..1e904776 100644 --- a/tests/Behat/features/attachments.feature +++ b/tests/Behat/features/attachments.feature @@ -5,7 +5,6 @@ Feature: Interacting with the REST API for attachments Scenario: Uploading an attachment Given I have a "NativeCurlClient" client - And I create a project with name "Test Project" and identifier "test-project" When I upload the content of the file "%tests_dir%/Fixtures/testfile_01.txt" with the following data | property | value | | filename | testfile.txt | @@ -25,3 +24,42 @@ Feature: Interacting with the REST API for attachments | property | value | | id | 1 | | token | 1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8 | + + @wip + 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 | From 03a02fd0d3b91e2cef082936235a3dc064ba8465 Mon Sep 17 00:00:00 2001 From: Art4 Date: Tue, 13 Feb 2024 22:39:42 +0100 Subject: [PATCH 30/40] remove e2e tests for attachments --- tests/Behat/features/attachments.feature | 1 - tests/End2End/Attachment/AttachmentTest.php | 68 --------------------- 2 files changed, 69 deletions(-) delete mode 100644 tests/End2End/Attachment/AttachmentTest.php diff --git a/tests/Behat/features/attachments.feature b/tests/Behat/features/attachments.feature index 1e904776..d04c370a 100644 --- a/tests/Behat/features/attachments.feature +++ b/tests/Behat/features/attachments.feature @@ -25,7 +25,6 @@ Feature: Interacting with the REST API for attachments | id | 1 | | token | 1.7b962f8af22e26802b87abfa0b07b21dbd03b984ec8d6888dabd3f69cff162f8 | - @wip 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 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 - ); - } -} From f8b7c9d1794cca6e407e4ba807247674f3647505 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 08:10:57 +0100 Subject: [PATCH 31/40] sort packages in composer.json --- composer.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 28e75642..3d7abb22 100644 --- a/composer.json +++ b/composer.json @@ -29,12 +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", - "behat/behat": "^3.14" + "phpunit/phpunit": "^9 || ^10.5" }, "autoload": { "psr-4": { @@ -46,6 +46,9 @@ "Redmine\\Tests\\": "tests/" } }, + "config": { + "sort-packages": true + }, "scripts": { "behat": "behat --config tests/Behat/behat.yml", "codestyle": "php-cs-fixer fix", From f710eb249fac47799a07424a3a3053d1d0707cbe Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 08:12:02 +0100 Subject: [PATCH 32/40] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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. From 23ef303da55e9729d679ba7444ba54b2986c0c3b Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 08:13:37 +0100 Subject: [PATCH 33/40] Remove internal mark from Response interface --- src/Redmine/Http/Response.php | 2 -- 1 file changed, 2 deletions(-) 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 { From f50d49969bca2aef6eac9573a969aa58c11aecf7 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 08:41:10 +0100 Subject: [PATCH 34/40] Add tests for wiki pages --- tests/Behat/Bootstrap/FeatureContext.php | 5 ++- tests/Behat/Bootstrap/WikiContextTrait.php | 32 ++++++++++++++++ tests/Behat/features/groups.feature | 4 +- tests/Behat/features/projects.feature | 4 +- tests/Behat/features/wiki.feature | 43 ++++++++++++++++++++++ 5 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 tests/Behat/Bootstrap/WikiContextTrait.php create mode 100644 tests/Behat/features/wiki.feature diff --git a/tests/Behat/Bootstrap/FeatureContext.php b/tests/Behat/Bootstrap/FeatureContext.php index a853099d..b5e083c4 100644 --- a/tests/Behat/Bootstrap/FeatureContext.php +++ b/tests/Behat/Bootstrap/FeatureContext.php @@ -28,6 +28,7 @@ final class FeatureContext extends TestCase implements Context use AttachmentContextTrait; use GroupContextTrait; use ProjectContextTrait; + use WikiContextTrait; private static ?BehatHookTracer $tracer = null; @@ -208,9 +209,9 @@ public function theReturnedDataPropertyIsAnArray($property) } /** - * @Then the returned data :property property containts :count items + * @Then the returned data :property property contains :count items */ - public function theReturnedDataPropertyContaintsItems($property, int $count) + public function theReturnedDataPropertyContainsItems($property, int $count) { $returnData = $this->getLastReturnAsArray(); diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php new file mode 100644 index 00000000..4f7067f7 --- /dev/null +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -0,0 +1,32 @@ +getNativeCurlClient()->getApi('wiki'); + + $this->registerClientResponse( + $api->create($identifier, $pageName, $data), + $api->getLastResponse() + ); + } +} diff --git a/tests/Behat/features/groups.feature b/tests/Behat/features/groups.feature index 03cdd00e..e6363ae2 100644 --- a/tests/Behat/features/groups.feature +++ b/tests/Behat/features/groups.feature @@ -29,7 +29,7 @@ Feature: Interacting with the REST API for groups groups """ And the returned data "groups" property is an array - And the returned data "groups" property containts "0" items + And the returned data "groups" property contains "0" items Scenario: Listing of one group Given I have a "NativeCurlClient" client @@ -42,7 +42,7 @@ Feature: Interacting with the REST API for groups groups """ And the returned data "groups" property is an array - And the returned data "groups" property containts "1" items + 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 """ diff --git a/tests/Behat/features/projects.feature b/tests/Behat/features/projects.feature index 6cc1ba58..b7079ae3 100644 --- a/tests/Behat/features/projects.feature +++ b/tests/Behat/features/projects.feature @@ -101,7 +101,7 @@ Feature: Interacting with the REST API for projects limit """ And the returned data "projects" property is an array - And the returned data "projects" property containts "0" items + And the returned data "projects" property contains "0" items And the returned data has proterties with the following data | property | value | | total_count | 0 | @@ -120,7 +120,7 @@ Feature: Interacting with the REST API for projects | offset | 0 | | limit | 25 | And the returned data "projects" property is an array - And the returned data "projects" property containts "1" items + 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" diff --git a/tests/Behat/features/wiki.feature b/tests/Behat/features/wiki.feature new file mode 100644 index 00000000..7aaf2ca7 --- /dev/null +++ b/tests/Behat/features/wiki.feature @@ -0,0 +1,43 @@ +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 | + From e6cf9e4a72d0d4461e2eddfa4485eb779824f721 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 09:32:28 +0100 Subject: [PATCH 35/40] Add scenario to show a wiki page --- tests/Behat/Bootstrap/WikiContextTrait.php | 26 ++++++++++++++ tests/Behat/features/wiki.feature | 42 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php index 4f7067f7..ab78f07b 100644 --- a/tests/Behat/Bootstrap/WikiContextTrait.php +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -10,6 +10,18 @@ trait WikiContextTrait { + /** + * @When I create a wiki page with name :pageName and project identifier :identifier + */ + public function iCreateAWikiPageWithNameAndProjectIdentifier(string $pageName, string $identifier) + { + $this->iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData( + $pageName, + $identifier, + new TableNode([['property', 'value']]) + ); + } + /** * @When I create a wiki page with name :pageName and project identifier :identifier with the following data */ @@ -29,4 +41,18 @@ public function iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData $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() + ); + } } diff --git a/tests/Behat/features/wiki.feature b/tests/Behat/features/wiki.feature index 7aaf2ca7..04bb8275 100644 --- a/tests/Behat/features/wiki.feature +++ b/tests/Behat/features/wiki.feature @@ -41,3 +41,45 @@ Feature: Interacting with the REST API for wikis | 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 | From 118902af70e760b7da4658365167008c38702314 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 09:58:16 +0100 Subject: [PATCH 36/40] Add scenarios for uploading attachments to wiki pages --- tests/Behat/Bootstrap/WikiContextTrait.php | 10 +- tests/Behat/features/wiki.feature | 120 +++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php index ab78f07b..6a6904ef 100644 --- a/tests/Behat/Bootstrap/WikiContextTrait.php +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -30,7 +30,15 @@ public function iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData $data = []; foreach ($table as $row) { - $data[$row['property']] = $row['value']; + $key = $row['property']; + $value = $row['value']; + + // Support for json in uploads + if ($key === 'uploads') { + $value = json_decode($value, true); + } + + $data[$key] = $value; } /** @var Wiki */ diff --git a/tests/Behat/features/wiki.feature b/tests/Behat/features/wiki.feature index 04bb8275..76fdca64 100644 --- a/tests/Behat/features/wiki.feature +++ b/tests/Behat/features/wiki.feature @@ -41,6 +41,48 @@ Feature: Interacting with the REST API for wikis | 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" @@ -83,3 +125,81 @@ Feature: Interacting with the REST API for wikis | 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 | From aa9da36a70967776556fe72c0239d7e03da3344e Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 10:16:50 +0100 Subject: [PATCH 37/40] Add scenario to update wiki page --- tests/Behat/Bootstrap/WikiContextTrait.php | 49 ++++++++++++++++------ tests/Behat/features/wiki.feature | 15 +++++++ 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php index 6a6904ef..0e47a525 100644 --- a/tests/Behat/Bootstrap/WikiContextTrait.php +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -27,19 +27,7 @@ public function iCreateAWikiPageWithNameAndProjectIdentifier(string $pageName, s */ public function iCreateAWikiPageWithNameAndProjectIdentifierWithTheFollowingData(string $pageName, string $identifier, TableNode $table) { - $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; - } + $data = $this->prepareWikiData($table); /** @var Wiki */ $api = $this->getNativeCurlClient()->getApi('wiki'); @@ -63,4 +51,39 @@ public function iShowTheWikiPageWithNameAndProjectIdentifier(string $pageName, s $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() + ); + } + + 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/features/wiki.feature b/tests/Behat/features/wiki.feature index 76fdca64..03a21437 100644 --- a/tests/Behat/features/wiki.feature +++ b/tests/Behat/features/wiki.feature @@ -203,3 +203,18 @@ Feature: Interacting with the REST API for wikis | property | value | | id | 1 | | name | Redmine Admin | + + @wip + 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 "" From c8dff79465fc06fff60ac043375747a381f86444 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 10:22:18 +0100 Subject: [PATCH 38/40] Add scenario for deleting a wiki page --- tests/Behat/Bootstrap/WikiContextTrait.php | 14 ++++++++++++++ tests/Behat/features/wiki.feature | 13 ++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/Behat/Bootstrap/WikiContextTrait.php b/tests/Behat/Bootstrap/WikiContextTrait.php index 0e47a525..289f6dfa 100644 --- a/tests/Behat/Bootstrap/WikiContextTrait.php +++ b/tests/Behat/Bootstrap/WikiContextTrait.php @@ -68,6 +68,20 @@ public function iUpdateTheWikiPageWithNameAndProjectIdentifierWithTheFollowingDa ); } + /** + * @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 = []; diff --git a/tests/Behat/features/wiki.feature b/tests/Behat/features/wiki.feature index 03a21437..08db58a8 100644 --- a/tests/Behat/features/wiki.feature +++ b/tests/Behat/features/wiki.feature @@ -204,7 +204,6 @@ Feature: Interacting with the REST API for wikis | id | 1 | | name | Redmine Admin | - @wip Scenario: Updating a wiki page Given I have a "NativeCurlClient" client And I create a project with name "Test Project" and identifier "test-project" @@ -218,3 +217,15 @@ Feature: Interacting with the REST API for wikis 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 "" From dd34b7a61191bbc0c22e7852c0eec304b16932d5 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 10:23:12 +0100 Subject: [PATCH 39/40] Remove e2e tests for wiki --- tests/End2End/Wiki/WikiTest.php | 100 -------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 tests/End2End/Wiki/WikiTest.php 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)); - } -} From b4c3e7a9b954537176a45f968cf38a9c4d1e45e6 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 14 Feb 2024 10:25:47 +0100 Subject: [PATCH 40/40] Remove e2e tests --- composer.json | 1 - phpunit-end2end.xml | 30 -------- tests/End2End/ClientTestCase.php | 37 ---------- tests/RedmineExtension/RedmineExtension.php | 21 ------ tests/RedmineExtension/TestRunnerTracer.php | 77 --------------------- 5 files changed, 166 deletions(-) delete mode 100644 phpunit-end2end.xml delete mode 100644 tests/End2End/ClientTestCase.php delete mode 100644 tests/RedmineExtension/RedmineExtension.php delete mode 100644 tests/RedmineExtension/TestRunnerTracer.php diff --git a/composer.json b/composer.json index 3d7abb22..1a5dd14b 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,6 @@ "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/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/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/RedmineExtension/RedmineExtension.php b/tests/RedmineExtension/RedmineExtension.php deleted file mode 100644 index e9511188..00000000 --- a/tests/RedmineExtension/RedmineExtension.php +++ /dev/null @@ -1,21 +0,0 @@ -registerTracer(new TestRunnerTracer()); - } -} diff --git a/tests/RedmineExtension/TestRunnerTracer.php b/tests/RedmineExtension/TestRunnerTracer.php deleted file mode 100644 index 95732573..00000000 --- a/tests/RedmineExtension/TestRunnerTracer.php +++ /dev/null @@ -1,77 +0,0 @@ -asId(), static::$instances)) { - RedmineInstance::create(static::$tracer, $redmineVersion); - } - - return static::$instances[$redmineVersion->asId()]; - } - - public function registerInstance(RedmineInstance $instance): void - { - static::$instances[$instance->getVersionId()] = $instance; - } - - public function deregisterInstance(RedmineInstance $instance): void - { - unset(static::$instances[$instance->getVersionId()]); - } - - public function trace(Event $event): void - { - if ($event instanceof TestRunnerStarted) { - static::$tracer = $this; - } - - if ($event instanceof TestFinished) { - foreach (static::$instances as $instance) { - $instance->reset($this); - } - } - - if ($event instanceof TestRunnerFinished) { - foreach (static::$instances as $instance) { - $instance->shutdown($this); - } - - static::$tracer = null; - } - } -}