From 06f6345b5945282ec45029d1ad69f544cd60dc1e Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 15:34:08 +0100 Subject: [PATCH 1/7] Move tests for Group::create into separate test case --- tests/Unit/Api/Group/CreateTest.php | 74 +++++++++++++++++++++++++++++ tests/Unit/Api/GroupTest.php | 70 +-------------------------- 2 files changed, 76 insertions(+), 68 deletions(-) create mode 100644 tests/Unit/Api/Group/CreateTest.php diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php new file mode 100644 index 00000000..06f28f02 --- /dev/null +++ b/tests/Unit/Api/Group/CreateTest.php @@ -0,0 +1,74 @@ + 'Group Name', + ]; + + // Create the used mock objects + $client = $this->createMock(Client::class); + $client->expects($this->once()) + ->method('requestPost') + ->with( + $this->logicalAnd( + $this->stringStartsWith('/groups'), + $this->logicalXor( + $this->stringEndsWith('.json'), + $this->stringEndsWith('.xml') + ) + ), + $this->stringContains('Group Name') + ) + ->willReturn(true); + $client->expects($this->exactly(1)) + ->method('getLastResponseBody') + ->willReturn($response); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $this->assertSame($response, $api->create($postParameter)); + } + + /** + * @covers ::create + */ + public function testCreateThrowsExceptionIfNameIsMissing() + { + // Test values + $postParameter = []; + + // Create the used mock objects + $client = $this->createMock(Client::class); + + // Create the object under test + $api = new Group($client); + + $this->expectException(MissingParameterException::class); + $this->expectExceptionMessage('Theses parameters are mandatory: `name`'); + + // Perform the tests + $api->create($postParameter); + } +} diff --git a/tests/Unit/Api/GroupTest.php b/tests/Unit/Api/GroupTest.php index fa5fc56a..8188a499 100644 --- a/tests/Unit/Api/GroupTest.php +++ b/tests/Unit/Api/GroupTest.php @@ -1,11 +1,12 @@ assertSame($expectedReturn, $api->remove(5)); } - /** - * Test create(). - * - * @covers ::create - * @covers ::post - * @test - */ - public function testCreateCallsPost() - { - // Test values - $response = 'API Response'; - $postParameter = [ - 'name' => 'Group Name', - ]; - - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestPost') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/groups'), - $this->logicalXor( - $this->stringEndsWith('.json'), - $this->stringEndsWith('.xml') - ) - ), - $this->stringContains('Group Name') - ) - ->willReturn(true); - $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); - - // Create the object under test - $api = new Group($client); - - // Perform the tests - $this->assertSame($response, $api->create($postParameter)); - } - - /** - * Test create(). - * - * @covers ::create - * @covers ::post - * - * @test - */ - public function testCreateThrowsExceptionIfNameIsMissing() - { - // Test values - $postParameter = []; - - // Create the used mock objects - $client = $this->createMock(Client::class); - - // Create the object under test - $api = new Group($client); - - $this->expectException(MissingParameterException::class); - $this->expectExceptionMessage('Theses parameters are mandatory: `name`'); - - // Perform the tests - $api->create($postParameter); - } - /** * Test removeUser(). * From 0cbf711cf994a7d1574bda67f7f26f8d4659c8e2 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 15:45:38 +0100 Subject: [PATCH 2/7] Add test to create group with user_ids --- src/Redmine/Api/Group.php | 3 +- tests/Unit/Api/Group/CreateTest.php | 87 +++++++++++++++++++---------- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Redmine/Api/Group.php b/src/Redmine/Api/Group.php index 206dcc62..56af78df 100644 --- a/src/Redmine/Api/Group.php +++ b/src/Redmine/Api/Group.php @@ -8,6 +8,7 @@ use Redmine\Exception\UnexpectedResponseException; use Redmine\Serializer\PathSerializer; use Redmine\Serializer\XmlSerializer; +use SimpleXMLElement; /** * Handling of groups. @@ -101,7 +102,7 @@ public function listing($forceUpdate = false) * * @throws MissingParameterException Missing mandatory parameters * - * @return string|false + * @return string|SimpleXMLElement|false */ public function create(array $params = []) { diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php index 06f28f02..14cdb897 100644 --- a/tests/Unit/Api/Group/CreateTest.php +++ b/tests/Unit/Api/Group/CreateTest.php @@ -6,49 +6,78 @@ use PHPUnit\Framework\TestCase; use Redmine\Api\Group; -use Redmine\Client\Client; use Redmine\Exception\MissingParameterException; +use Redmine\Http\HttpClient; +use Redmine\Http\Response; +use SimpleXMLElement; /** * @covers \Redmine\Api\Group::create */ class CreateTest extends TestCase { - /** - * @covers ::create - */ - public function testCreateCallsPost() + public function testCreateWithNameCreatesGroup() { - // Test values - $response = 'API Response'; - $postParameter = [ - 'name' => 'Group Name', - ]; + $client = $this->createMock(HttpClient::class); + $client->expects($this->exactly(1)) + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('POST', $method); + $this->assertSame('/groups.xml', $path); + $this->assertXmlStringEqualsXmlString('Group Name', $body); - // Create the used mock objects - $client = $this->createMock(Client::class); - $client->expects($this->once()) - ->method('requestPost') - ->with( - $this->logicalAnd( - $this->stringStartsWith('/groups'), - $this->logicalXor( - $this->stringEndsWith('.json'), - $this->stringEndsWith('.xml') - ) - ), - $this->stringContains('Group Name') - ) - ->willReturn(true); + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $xmlElement = $api->create(['name' => 'Group Name']); + + $this->assertInstanceOf(SimpleXMLElement::class, $xmlElement); + $this->assertXmlStringEqualsXmlString( + '', + $xmlElement->asXml(), + ); + } + + public function testCreateWithNameAndUserIdsCreatesGroup() + { + $client = $this->createMock(HttpClient::class); $client->expects($this->exactly(1)) - ->method('getLastResponseBody') - ->willReturn($response); + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('POST', $method); + $this->assertSame('/groups.xml', $path); + $this->assertXmlStringEqualsXmlString('Group Name123', $body); + + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); // Create the object under test $api = new Group($client); // Perform the tests - $this->assertSame($response, $api->create($postParameter)); + $xmlElement = $api->create(['name' => 'Group Name', 'user_ids' => [1, 2, 3]]); + + $this->assertInstanceOf(SimpleXMLElement::class, $xmlElement); + $this->assertXmlStringEqualsXmlString( + '', + $xmlElement->asXml(), + ); } /** @@ -60,7 +89,7 @@ public function testCreateThrowsExceptionIfNameIsMissing() $postParameter = []; // Create the used mock objects - $client = $this->createMock(Client::class); + $client = $this->createMock(HttpClient::class); // Create the object under test $api = new Group($client); From eee0290ac4a0a033484aa1b68bc1c1a6c5b29922 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 16:19:21 +0100 Subject: [PATCH 3/7] Add test to create group with custom fields --- tests/Unit/Api/Group/CreateTest.php | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php index 14cdb897..fa852f01 100644 --- a/tests/Unit/Api/Group/CreateTest.php +++ b/tests/Unit/Api/Group/CreateTest.php @@ -80,6 +80,43 @@ public function testCreateWithNameAndUserIdsCreatesGroup() ); } + public function testCreateWithNameAndCustomFieldsCreatesGroup() + { + $client = $this->createMock(HttpClient::class); + $client->expects($this->exactly(1)) + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('POST', $method); + $this->assertSame('/groups.xml', $path); + $this->assertXmlStringEqualsXmlString('Group Name5', $body); + + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $xmlElement = $api->create([ + 'name' => 'Group Name', + 'custom_fields' => [ + ['id' => 1, 'value' => 5], + ], + ]); + + $this->assertInstanceOf(SimpleXMLElement::class, $xmlElement); + $this->assertXmlStringEqualsXmlString( + '', + $xmlElement->asXml(), + ); + } + /** * @covers ::create */ From 81a80d5b5c30ffac945068aeda834ffd32665130 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 16:19:43 +0100 Subject: [PATCH 4/7] Add docs to create group with custom fields --- docs/usage.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index f0c5ac4a..001fa046 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -489,6 +489,13 @@ $client->getApi('group')->removeUser($groupId, $userId); $client->getApi('group')->create([ 'name' => 'asdf', 'user_ids' => [1, 2], + 'custom_fields' => [ + [ + 'id' => 123, + 'name' => 'cf_name', + 'value' => 'cf_value', + ], + ], ]); // ---------------------------- From 8e1bf630c632ccef36c75cccb459d5166df04c3f Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 16:51:09 +0100 Subject: [PATCH 5/7] implement Group::update() --- src/Redmine/Api/Group.php | 19 +++-- tests/Unit/Api/Group/CreateTest.php | 3 - tests/Unit/Api/Group/UpdateTest.php | 105 ++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 tests/Unit/Api/Group/UpdateTest.php diff --git a/src/Redmine/Api/Group.php b/src/Redmine/Api/Group.php index 56af78df..4ee6442d 100644 --- a/src/Redmine/Api/Group.php +++ b/src/Redmine/Api/Group.php @@ -125,17 +125,28 @@ public function create(array $params = []) } /** + * Updates a group. + * * NOT DOCUMENTED in Redmine's wiki. * * @see http://www.redmine.org/projects/redmine/wiki/Rest_Groups#PUT * - * @param int $id + * @param int $id the group id * - * @throws Exception Not implemented + * @return string empty string */ - public function update($id, array $params = []) + public function update(int $id, array $params = []) { - throw new \Exception('Not implemented'); + $defaults = [ + 'name' => null, + 'user_ids' => null, + ]; + $params = $this->sanitizeParams($defaults, $params); + + return $this->put( + '/groups/' . $id . '.xml', + XmlSerializer::createFromArray(['group' => $params])->getEncoded() + ); } /** diff --git a/tests/Unit/Api/Group/CreateTest.php b/tests/Unit/Api/Group/CreateTest.php index fa852f01..7e34cdce 100644 --- a/tests/Unit/Api/Group/CreateTest.php +++ b/tests/Unit/Api/Group/CreateTest.php @@ -117,9 +117,6 @@ public function testCreateWithNameAndCustomFieldsCreatesGroup() ); } - /** - * @covers ::create - */ public function testCreateThrowsExceptionIfNameIsMissing() { // Test values diff --git a/tests/Unit/Api/Group/UpdateTest.php b/tests/Unit/Api/Group/UpdateTest.php new file mode 100644 index 00000000..08bdaed6 --- /dev/null +++ b/tests/Unit/Api/Group/UpdateTest.php @@ -0,0 +1,105 @@ +createMock(HttpClient::class); + $client->expects($this->exactly(1)) + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('PUT', $method); + $this->assertSame('/groups/1.xml', $path); + $this->assertXmlStringEqualsXmlString('Group Name', $body); + + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $return = $api->update(1, ['name' => 'Group Name']); + + $this->assertSame('', $return); + } + + public function testUpdateWithUserIdsUpdatesGroup() + { + $client = $this->createMock(HttpClient::class); + $client->expects($this->exactly(1)) + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('PUT', $method); + $this->assertSame('/groups/1.xml', $path); + $this->assertXmlStringEqualsXmlString('123', $body); + + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $return = $api->update(1, ['user_ids' => [1, 2, 3]]); + + $this->assertSame('', $return); + } + + public function testUpdateWithCustomFieldsUpdatesGroup() + { + $client = $this->createMock(HttpClient::class); + $client->expects($this->exactly(1)) + ->method('request') + ->willReturnCallback(function (string $method, string $path, string $body = '') { + $this->assertSame('PUT', $method); + $this->assertSame('/groups/1.xml', $path); + $this->assertXmlStringEqualsXmlString('5', $body); + + return $this->createConfiguredMock( + Response::class, + [ + 'getContentType' => 'application/xml', + 'getBody' => '', + ] + ); + }); + + // Create the object under test + $api = new Group($client); + + // Perform the tests + $return = $api->update(1, [ + 'custom_fields' => [ + ['id' => 1, 'value' => 5], + ], + ]); + + $this->assertSame('', $return); + } +} From 10260fb35e37275c5fce75fd2eaaad42fa4dfd2c Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 16:55:22 +0100 Subject: [PATCH 6/7] Fix tests --- tests/Integration/GroupXmlTest.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/tests/Integration/GroupXmlTest.php b/tests/Integration/GroupXmlTest.php index 3f7af662..6b64ce41 100644 --- a/tests/Integration/GroupXmlTest.php +++ b/tests/Integration/GroupXmlTest.php @@ -2,7 +2,6 @@ namespace Redmine\Tests\Integration; -use Exception; use PHPUnit\Framework\TestCase; use Redmine\Exception\MissingParameterException; use Redmine\Tests\Fixtures\MockClient; @@ -48,15 +47,30 @@ public function testCreateComplex() ); } - public function testUpdateNotImplemented() + public function testUpdateComplex() { /** @var \Redmine\Api\Group */ $api = MockClient::create()->getApi('group'); - $this->assertInstanceOf('Redmine\Api\Group', $api); - - $this->expectException(Exception::class); - $this->expectExceptionMessage('Not implemented'); + $res = $api->update(5, [ + 'name' => 'Developers', + 'user_ids' => [3, 5], + ]); + $response = json_decode($res, true); - $api->update(1); + $this->assertEquals('PUT', $response['method']); + $this->assertEquals('/groups/5.xml', $response['path']); + $this->assertXmlStringEqualsXmlString( + <<< XML + + + Developers + + 3 + 5 + + + XML, + $response['data'] + ); } } From 60cdb51c80b9fb507e296343fc954239b0a01af9 Mon Sep 17 00:00:00 2001 From: Art4 Date: Wed, 10 Jan 2024 16:59:56 +0100 Subject: [PATCH 7/7] Update CHANGELOG.md and docs --- CHANGELOG.md | 4 ++++ docs/usage.md | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c8e9e6c..fcde1f9b 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.4.0...v2.x) +### Added + +- Added support for updating groups. + ### Changed - The last response is saved in `Redmine\Api\AbstractApi` to prevent race conditions with `Redmine\Client\Client` implementations. diff --git a/docs/usage.md b/docs/usage.md index 001fa046..d195eccd 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -497,6 +497,18 @@ $client->getApi('group')->create([ ], ], ]); +$client->getApi('group')->update($groupId, [ + 'name' => 'asdf', + // Note: you can only add users this way; use removeUser to remove a user + 'user_ids' => [1, 2], + 'custom_fields' => [ + [ + 'id' => 123, + 'name' => 'cf_name', + 'value' => 'cf_value', + ], + ], +]); // ---------------------------- // Project memberships