diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8125f5..f3102ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New method `Redmine\Api\TimeEntryActivity::listNames()` for listing the ids and names of all time entry activities. - New method `Redmine\Api\Tracker::listNames()` for listing the ids and names of all trackers. - New method `Redmine\Api\User::listLogins()` for listing the ids and logins of all users. +- New method `Redmine\Api\Version::listNamesByProject()` for listing the ids and names of all versions of a project. ### Deprecated @@ -30,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Redmine\Api\TimeEntryActivity::listing()` is deprecated, use `\Redmine\Api\TimeEntryActivity::listNames()` instead. - `Redmine\Api\Tracker::listing()` is deprecated, use `\Redmine\Api\Tracker::listNames()` instead. - `Redmine\Api\User::listing()` is deprecated, use `\Redmine\Api\User::listLogins()` instead. +- `Redmine\Api\Version::listing()` is deprecated, use `\Redmine\Api\Version::listNamesByProject()` instead. ## [v2.6.0](https://github.com/kbsali/php-redmine-api/compare/v2.5.0...v2.6.0) - 2024-03-25 diff --git a/src/Redmine/Api/Version.php b/src/Redmine/Api/Version.php index 518d3cb0..a7fd51ab 100644 --- a/src/Redmine/Api/Version.php +++ b/src/Redmine/Api/Version.php @@ -23,6 +23,8 @@ class Version extends AbstractApi { private $versions = []; + private $versionNames = []; + /** * List versions of a project. * @@ -51,6 +53,41 @@ final public function listByProject($projectIdentifier, array $params = []): arr } } + /** + * Returns an array of all versions by a project with id/name pairs. + * + * @param string|int $projectIdentifier project id or literal identifier + * + * @throws InvalidParameterException if $projectIdentifier is not of type int or string + * + * @return array list of version names (id => name) + */ + final public function listNamesByProject($projectIdentifier): array + { + if (! is_int($projectIdentifier) && ! is_string($projectIdentifier)) { + throw new InvalidParameterException(sprintf( + '%s(): Argument #1 ($projectIdentifier) must be of type int or string', + __METHOD__, + )); + } + + if (array_key_exists($projectIdentifier, $this->versionNames)) { + return $this->versionNames[$projectIdentifier]; + } + + $this->versionNames[$projectIdentifier] = []; + + $list = $this->listByProject($projectIdentifier); + + if (array_key_exists('versions', $list)) { + foreach ($list['versions'] as $version) { + $this->versionNames[$projectIdentifier][(int) $version['id']] = $version['name']; + } + } + + return $this->versionNames[$projectIdentifier]; + } + /** * List versions. * @@ -88,6 +125,9 @@ public function all($project, array $params = []) /** * Returns an array of name/id pairs (or id/name if not $reverse) of versions for $project. * + * @deprecated v2.7.0 Use listNamesByProject() instead. + * @see Version::listNamesByProject() + * * @param string|int $project project id or literal identifier * @param bool $forceUpdate to force the update of the projects var * @param bool $reverse to return an array indexed by name rather than id @@ -97,6 +137,8 @@ public function all($project, array $params = []) */ public function listing($project, $forceUpdate = false, $reverse = true, array $params = []) { + @trigger_error('`' . __METHOD__ . '()` is deprecated since v2.7.0, use `' . __CLASS__ . '::listNamesByProject()` instead.', E_USER_DEPRECATED); + if (true === $forceUpdate || empty($this->versions)) { $this->versions = $this->listByProject($project, $params); } diff --git a/tests/Behat/Bootstrap/VersionContextTrait.php b/tests/Behat/Bootstrap/VersionContextTrait.php index 13c9d9ba..250641a6 100644 --- a/tests/Behat/Bootstrap/VersionContextTrait.php +++ b/tests/Behat/Bootstrap/VersionContextTrait.php @@ -45,35 +45,63 @@ public function iCreateAVersionWithProjectIdentifierAndWithTheFollowingData(stri } /** - * @When I update the version with id :id and the following data + * @When I show the version with id :versionId */ - public function iUpdateTheVersionWithIdAndTheFollowingData($id, TableNode $table) + public function iShowTheVersionWithId(int $versionId) { - $data = []; + /** @var Version */ + $api = $this->getNativeCurlClient()->getApi('version'); - foreach ($table as $row) { - $data[$row['property']] = $row['value']; - } + $this->registerClientResponse( + $api->show($versionId), + $api->getLastResponse(), + ); + } + /** + * @When I list all versions for project identifier :identifier + */ + public function iListAllVersionsForProjectIdentifier($identifier) + { /** @var Version */ $api = $this->getNativeCurlClient()->getApi('version'); $this->registerClientResponse( - $api->update($id, $data), + $api->listByProject($identifier), $api->getLastResponse(), ); } /** - * @When I show the version with id :versionId + * @When I list all version names for project identifier :identifier */ - public function iShowTheVersionWithId(int $versionId) + public function iListAllVersionNamesForProjectIdentifier($identifier) { /** @var Version */ $api = $this->getNativeCurlClient()->getApi('version'); $this->registerClientResponse( - $api->show($versionId), + $api->listNamesByProject($identifier), + $api->getLastResponse(), + ); + } + + /** + * @When I update the version with id :id and the following data + */ + public function iUpdateTheVersionWithIdAndTheFollowingData($id, TableNode $table) + { + $data = []; + + foreach ($table as $row) { + $data[$row['property']] = $row['value']; + } + + /** @var Version */ + $api = $this->getNativeCurlClient()->getApi('version'); + + $this->registerClientResponse( + $api->update($id, $data), $api->getLastResponse(), ); } diff --git a/tests/Behat/features/version.feature b/tests/Behat/features/version.feature index 72ac2434..263fd7e0 100644 --- a/tests/Behat/features/version.feature +++ b/tests/Behat/features/version.feature @@ -52,20 +52,6 @@ Feature: Interacting with the REST API for versions | id | 1 | | name | Test Project | - Scenario: Updating a version - Given I have a "NativeCurlClient" client - And I create a project with name "Test Project" and identifier "test-project" - And I create a version with project identifier "test-project" with the following data - | property | value | - | name | Test-Version | - When I update the version with id "1" and the following data - | property | value | - | name | New Version 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 "" - Scenario: Showing a version Given I have a "NativeCurlClient" client And I create a project with name "Test Project" and identifier "test-project" @@ -115,6 +101,99 @@ Feature: Interacting with the REST API for versions And the response has the content "" And the returned data is false + Scenario: Listing of zero versions + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + When I list all versions for 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 + """ + versions + total_count + """ + And the returned data contains the following data + | property | value | + | versions | [] | + | total_count | 0 | + + Scenario: Listing of multiple versions + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I create a version with name "Test-Version B" and project identifier "test-project" + And I create a version with name "Test-Version A" and project identifier "test-project" + When I list all versions for 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 + """ + versions + total_count + """ + And the returned data contains the following data + | property | value | + | total_count | 2 | + And the returned data "versions" property is an array + And the returned data "versions" property contains "2" items + And the returned data "versions.0" property is an array + And the returned data "versions.0" property has only the following properties + """ + id + project + name + description + status + due_date + sharing + wiki_page_title + created_on + updated_on + """ + And the returned data "versions.0" property contains the following data + | property | value | + | id | 1 | + | name | Test-Version B | + | description | | + | status | open | + | sharing | none | + | wiki_page_title | null | + And the returned data "versions.0.project" property contains the following data + | property | value | + | id | 1 | + | name | Test Project | + + @wip + Scenario: Listing of multiple version names + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project 1" and identifier "test-project-1" + And I create a project with name "Test Project 2" and identifier "test-project-2" + And I create a version with name "Test-Version 1B" and project identifier "test-project-1" + And I create a version with name "Test-Version 1A" and project identifier "test-project-1" + And I create a version with name "Test-Version 2B" and project identifier "test-project-2" + And I create a version with name "Test-Version 2A" and project identifier "test-project-2" + When I list all version names for project identifier "test-project-2" + Then the response has the status code "200" + And the response has the content type "application/json" + And the returned data contains "2" items + And the returned data contains the following data + | property | value | + | 3 | Test-Version 2B | + | 4 | Test-Version 2A | + + Scenario: Updating a version + Given I have a "NativeCurlClient" client + And I create a project with name "Test Project" and identifier "test-project" + And I create a version with project identifier "test-project" with the following data + | property | value | + | name | Test-Version | + When I update the version with id "1" and the following data + | property | value | + | name | New Version 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 "" + Scenario: Removing a version Given I have a "NativeCurlClient" client And I create a project with name "Test Project" and identifier "test-project" diff --git a/tests/Unit/Api/Version/ListNamesByProjectTest.php b/tests/Unit/Api/Version/ListNamesByProjectTest.php new file mode 100644 index 00000000..65c3b8a0 --- /dev/null +++ b/tests/Unit/Api/Version/ListNamesByProjectTest.php @@ -0,0 +1,128 @@ +assertSame($expectedResponse, $api->listNamesByProject($projectIdentifier)); + } + + public static function getListNamesByProjectData(): array + { + return [ + 'test without versions' => [ + 5, + '/projects/5/versions.json', + 201, + << [ + 'test-project', + '/projects/test-project/versions.json', + 201, + << "Version 3", + 8 => "Version 2", + 9 => "Version 1", + ], + ], + ]; + } + + public function testListNamesByProjectCallsHttpClientOnlyOnce() + { + $client = AssertingHttpClient::create( + $this, + [ + 'GET', + '/projects/5/versions.json', + 'application/json', + '', + 200, + 'application/json', + <<assertSame([1 => 'Version 1'], $api->listNamesByProject(5)); + $this->assertSame([1 => 'Version 1'], $api->listNamesByProject(5)); + $this->assertSame([1 => 'Version 1'], $api->listNamesByProject(5)); + } + + /** + * @dataProvider Redmine\Tests\Fixtures\TestDataProvider::getInvalidProjectIdentifiers + */ + #[DataProviderExternal(TestDataProvider::class, 'getInvalidProjectIdentifiers')] + public function testListNamesByProjectWithWrongProjectIdentifierThrowsException($projectIdentifier) + { + $api = new Version($this->createMock(HttpClient::class)); + + $this->expectException(InvalidParameterException::class); + $this->expectExceptionMessage('Redmine\Api\Version::listNamesByProject(): Argument #1 ($projectIdentifier) must be of type int or string'); + + $api->listNamesByProject($projectIdentifier); + } +} diff --git a/tests/Unit/Api/VersionTest.php b/tests/Unit/Api/VersionTest.php index 6bbbfa7c..0ed55c06 100644 --- a/tests/Unit/Api/VersionTest.php +++ b/tests/Unit/Api/VersionTest.php @@ -246,6 +246,38 @@ public function testListingCallsGetEveryTimeWithForceUpdate() $this->assertSame($expectedReturn, $api->listing(5, true)); } + /** + * Test listing(). + */ + public function testListingTriggersDeprecationWarning() + { + $client = $this->createMock(Client::class); + $client->method('requestGet') + ->willReturn(true); + $client->method('getLastResponseBody') + ->willReturn('{"versions":[{"id":1,"name":"Version 1"},{"id":5,"name":"Version 5"}]}'); + $client->method('getLastResponseContentType') + ->willReturn('application/json'); + + $api = new Version($client); + + // PHPUnit 10 compatible way to test trigger_error(). + set_error_handler( + function ($errno, $errstr): bool { + $this->assertSame( + '`Redmine\Api\Version::listing()` is deprecated since v2.7.0, use `Redmine\Api\Version::listNamesByProject()` instead.', + $errstr, + ); + + restore_error_handler(); + return true; + }, + E_USER_DEPRECATED, + ); + + $api->listing(5); + } + /** * Test getIdByName(). */ @@ -282,7 +314,6 @@ public function testGetIdByNameMakesGetRequest() * * @dataProvider invalidSharingProvider * - * * @param string $sharingValue */ #[DataProvider('invalidSharingProvider')]