Skip to content

Commit 3de40b4

Browse files
authored
Merge pull request #411 from Art4/add-project-listnames
Add `Project::listNames()` method as replacement for `Project::listing()`
2 parents a3c0dd7 + 44719cd commit 3de40b4

File tree

6 files changed

+292
-0
lines changed

6 files changed

+292
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- New method `Redmine\Api\Group::listNames()` for listing the ids and names of all groups.
1414
- New method `Redmine\Api\IssueCategory::listNamesByProject()` for listing the ids and names of all issue categories of a project.
1515
- New method `Redmine\Api\IssueStatus::listNames()` for listing the ids and names of all issue statuses.
16+
- New method `Redmine\Api\Project::listNames()` for listing the ids and names of all projects.
1617

1718
### Deprecated
1819

1920
- `Redmine\Api\CustomField::listing()` is deprecated, use `\Redmine\Api\CustomField::listNames()` instead.
2021
- `Redmine\Api\Group::listing()` is deprecated, use `\Redmine\Api\Group::listNames()` instead.
2122
- `Redmine\Api\IssueCategory::listing()` is deprecated, use `\Redmine\Api\IssueCategory::listNamesByProject()` instead.
2223
- `Redmine\Api\IssueStatus::listing()` is deprecated, use `\Redmine\Api\IssueStatus::listNamesByProject()` instead.
24+
- `Redmine\Api\Project::listing()` is deprecated, use `\Redmine\Api\Project::listNamesByProject()` instead.
2325

2426
## [v2.6.0](https://github.com/kbsali/php-redmine-api/compare/v2.5.0...v2.6.0) - 2024-03-25
2527

src/Redmine/Api/Project.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Project extends AbstractApi
2424
{
2525
private $projects = [];
2626

27+
private $projectNames = null;
28+
2729
/**
2830
* List projects.
2931
*
@@ -44,6 +46,43 @@ final public function list(array $params = []): array
4446
}
4547
}
4648

49+
/**
50+
* Returns an array of all projects with id/name pairs.
51+
*
52+
* @return array<int,string> list of projects (id => name)
53+
*/
54+
final public function listNames(): array
55+
{
56+
if ($this->projectNames !== null) {
57+
return $this->projectNames;
58+
}
59+
60+
$this->projectNames = [];
61+
62+
$limit = 100;
63+
$offset = 0;
64+
65+
do {
66+
$list = $this->list([
67+
'limit' => $limit,
68+
'offset' => $offset,
69+
]);
70+
71+
$listCount = 0;
72+
$offset += $limit;
73+
74+
if (array_key_exists('projects', $list)) {
75+
$listCount = count($list['projects']);
76+
77+
foreach ($list['projects'] as $issueStatus) {
78+
$this->projectNames[(int) $issueStatus['id']] = (string) $issueStatus['name'];
79+
}
80+
}
81+
} while ($listCount === $limit);
82+
83+
return $this->projectNames;
84+
}
85+
4786
/**
4887
* List projects.
4988
*
@@ -80,6 +119,9 @@ public function all(array $params = [])
80119
/**
81120
* Returns an array of projects with name/id pairs (or id/name if $reserse is false).
82121
*
122+
* @deprecated v2.7.0 Use listNames() instead.
123+
* @see Project::listNames()
124+
*
83125
* @param bool $forceUpdate to force the update of the projects var
84126
* @param bool $reverse to return an array indexed by name rather than id
85127
* @param array $params to allow offset/limit (and more) to be passed
@@ -88,6 +130,8 @@ public function all(array $params = [])
88130
*/
89131
public function listing($forceUpdate = false, $reverse = true, array $params = [])
90132
{
133+
@trigger_error('`' . __METHOD__ . '()` is deprecated since v2.7.0, use `' . __CLASS__ . '::listNames()` instead.', E_USER_DEPRECATED);
134+
91135
if (true === $forceUpdate || empty($this->projects)) {
92136
$this->projects = $this->list($params);
93137
}

tests/Behat/Bootstrap/ProjectContextTrait.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ public function iCreateAProjectWithTheFollowingData(TableNode $table)
4343
);
4444
}
4545

46+
/**
47+
* @Given I create :count projects
48+
*/
49+
public function iCreateProjects(int $count)
50+
{
51+
while ($count > 0) {
52+
$this->iCreateAProjectWithNameAndIdentifier('Test Project ' . $count, 'test-project-' . $count);
53+
54+
$count--;
55+
}
56+
}
57+
4658
/**
4759
* @When I list all projects
4860
*/
@@ -57,6 +69,20 @@ public function iListAllProjects()
5769
);
5870
}
5971

72+
/**
73+
* @When I list all project names
74+
*/
75+
public function iListAllProjectNames()
76+
{
77+
/** @var Project */
78+
$api = $this->getNativeCurlClient()->getApi('project');
79+
80+
$this->registerClientResponse(
81+
$api->listNames(),
82+
$api->getLastResponse(),
83+
);
84+
}
85+
6086
/**
6187
* @When I show the project with identifier :identifier
6288
*/

tests/Behat/features/projects.feature

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,27 @@ Feature: Interacting with the REST API for projects
150150
updated_on
151151
"""
152152

153+
Scenario: Listing of multiple project names
154+
Given I have a "NativeCurlClient" client
155+
And I create a project with name "Test Project B" and identifier "test-project1"
156+
And I create a project with name "Test Project A" and identifier "test-project2"
157+
When I list all project names
158+
Then the response has the status code "200"
159+
And the response has the content type "application/json"
160+
And the returned data contains "2" items
161+
And the returned data has proterties with the following data
162+
| property | value |
163+
| 1 | Test Project B |
164+
| 2 | Test Project A |
165+
166+
Scenario: Listing of multiple project names
167+
Given I have a "NativeCurlClient" client
168+
And I create "108" projects
169+
When I list all project names
170+
Then the response has the status code "200"
171+
And the response has the content type "application/json"
172+
And the returned data contains "108" items
173+
153174
Scenario: Updating a project
154175
Given I have a "NativeCurlClient" client
155176
And I create a project with name "Test Project" and identifier "test-project"
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Redmine\Tests\Unit\Api\Project;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\TestCase;
10+
use Redmine\Api\Project;
11+
use Redmine\Tests\Fixtures\AssertingHttpClient;
12+
13+
#[CoversClass(Project::class)]
14+
class ListNamesTest extends TestCase
15+
{
16+
/**
17+
* @dataProvider getListNamesData
18+
*/
19+
#[DataProvider('getListNamesData')]
20+
public function testListNamesReturnsCorrectResponse($expectedPath, $responseCode, $response, $expectedResponse)
21+
{
22+
$client = AssertingHttpClient::create(
23+
$this,
24+
[
25+
'GET',
26+
$expectedPath,
27+
'application/json',
28+
'',
29+
$responseCode,
30+
'application/json',
31+
$response,
32+
],
33+
);
34+
35+
// Create the object under test
36+
$api = new Project($client);
37+
38+
// Perform the tests
39+
$this->assertSame($expectedResponse, $api->listNames());
40+
}
41+
42+
public static function getListNamesData(): array
43+
{
44+
return [
45+
'test without projects' => [
46+
'/projects.json?limit=100&offset=0',
47+
201,
48+
<<<JSON
49+
{
50+
"projects": []
51+
}
52+
JSON,
53+
[],
54+
],
55+
'test with multiple projects' => [
56+
'/projects.json?limit=100&offset=0',
57+
201,
58+
<<<JSON
59+
{
60+
"projects": [
61+
{"id": 7, "name": "Project C"},
62+
{"id": 8, "name": "Project B"},
63+
{"id": 9, "name": "Project A"}
64+
]
65+
}
66+
JSON,
67+
[
68+
7 => "Project C",
69+
8 => "Project B",
70+
9 => "Project A",
71+
],
72+
],
73+
];
74+
}
75+
76+
public function testListNamesWithALotOfProjectsHandlesPagination()
77+
{
78+
$assertData = [];
79+
$projectsRequest1 = [];
80+
$projectsRequest2 = [];
81+
$projectsRequest3 = [];
82+
83+
for ($i = 1; $i <= 100; $i++) {
84+
$name = 'Project ' . $i;
85+
86+
$assertData[$i] = $name;
87+
$projectsRequest1[] = ['id' => $i, 'name' => $name];
88+
}
89+
90+
for ($i = 101; $i <= 200; $i++) {
91+
$name = 'Project ' . $i;
92+
93+
$assertData[$i] = $name;
94+
$projectsRequest2[] = ['id' => $i, 'name' => $name];
95+
}
96+
97+
$client = AssertingHttpClient::create(
98+
$this,
99+
[
100+
'GET',
101+
'/projects.json?limit=100&offset=0',
102+
'application/json',
103+
'',
104+
200,
105+
'application/json',
106+
json_encode(['projects' => $projectsRequest1]),
107+
],
108+
[
109+
'GET',
110+
'/projects.json?limit=100&offset=100',
111+
'application/json',
112+
'',
113+
200,
114+
'application/json',
115+
json_encode(['projects' => $projectsRequest2]),
116+
],
117+
[
118+
'GET',
119+
'/projects.json?limit=100&offset=200',
120+
'application/json',
121+
'',
122+
200,
123+
'application/json',
124+
json_encode(['projects' => $projectsRequest3]),
125+
],
126+
);
127+
128+
// Create the object under test
129+
$api = new Project($client);
130+
131+
// Perform the tests
132+
$this->assertSame($assertData, $api->listNames());
133+
}
134+
135+
public function testListNamesCallsHttpClientOnlyOnce()
136+
{
137+
$client = AssertingHttpClient::create(
138+
$this,
139+
[
140+
'GET',
141+
'/projects.json?limit=100&offset=0',
142+
'application/json',
143+
'',
144+
200,
145+
'application/json',
146+
<<<JSON
147+
{
148+
"projects": [
149+
{
150+
"id": 1,
151+
"name": "Project 1"
152+
}
153+
]
154+
}
155+
JSON,
156+
],
157+
);
158+
159+
// Create the object under test
160+
$api = new Project($client);
161+
162+
// Perform the tests
163+
$this->assertSame([1 => 'Project 1'], $api->listNames());
164+
$this->assertSame([1 => 'Project 1'], $api->listNames());
165+
$this->assertSame([1 => 'Project 1'], $api->listNames());
166+
}
167+
}

tests/Unit/Api/ProjectTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,38 @@ public function testListingCallsGetEveryTimeWithForceUpdate()
217217
$this->assertSame($expectedReturn, $api->listing(true));
218218
}
219219

220+
/**
221+
* Test listing().
222+
*/
223+
public function testListingTriggersDeprecationWarning()
224+
{
225+
$client = $this->createMock(Client::class);
226+
$client->method('requestGet')
227+
->willReturn(true);
228+
$client->method('getLastResponseBody')
229+
->willReturn('{"projects":[{"id":1,"name":"Project 1"},{"id":5,"name":"Project 5"}]}');
230+
$client->method('getLastResponseContentType')
231+
->willReturn('application/json');
232+
233+
$api = new Project($client);
234+
235+
// PHPUnit 10 compatible way to test trigger_error().
236+
set_error_handler(
237+
function ($errno, $errstr): bool {
238+
$this->assertSame(
239+
'`Redmine\Api\Project::listing()` is deprecated since v2.7.0, use `Redmine\Api\Project::listNames()` instead.',
240+
$errstr,
241+
);
242+
243+
restore_error_handler();
244+
return true;
245+
},
246+
E_USER_DEPRECATED,
247+
);
248+
249+
$api->listing();
250+
}
251+
220252
/**
221253
* Test getIdByName().
222254
*/

0 commit comments

Comments
 (0)