From 08db4341768f4d5e241059a0ec689a6936cf0224 Mon Sep 17 00:00:00 2001 From: Ondrej Machulda Date: Sun, 26 Nov 2017 12:45:50 +0100 Subject: [PATCH 1/2] Add isSuccessful method to command responses --- CHANGELOG.md | 1 + src/Model/CommandResponse.php | 5 +++ tests/Model/CommandResponseTest.php | 48 +++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ca4763..ea28ef3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Added - `UserMerge` command and `addUserMerge` & `addUserMerges` method for `EventsRequestBuilder`. (Accessible via `$matej->events()->...`). - `Interaction` command and `addInteraction` & `addInteractions` method for `EventsRequestBuilder`. (Accessible via `$matej->events()->...`). +- Method `isSuccessfull()` of `CommandResponse` for easy and encapsulated detection of successful command responses. ### Fixed - Return types of methods in `RequestBuilderFactory` were not defined (and were not guessable by an IDE) for PHP 5 version. diff --git a/src/Model/CommandResponse.php b/src/Model/CommandResponse.php index 01c1872..b575b53 100644 --- a/src/Model/CommandResponse.php +++ b/src/Model/CommandResponse.php @@ -53,4 +53,9 @@ public function getData(): array { return $this->data; } + + public function isSuccessful(): bool + { + return $this->getStatus() === self::STATUS_OK; + } } diff --git a/tests/Model/CommandResponseTest.php b/tests/Model/CommandResponseTest.php index 84663cc..e5638df 100644 --- a/tests/Model/CommandResponseTest.php +++ b/tests/Model/CommandResponseTest.php @@ -32,32 +32,60 @@ public function provideObjectResponses(): array { return [ 'OK response with only status' => [ - (object) ['status' => 'OK'], - 'OK', + (object) ['status' => CommandResponse::STATUS_OK], + CommandResponse::STATUS_OK, '', [], ], 'OK response with status and empty message and data' => [ - (object) ['status' => 'OK', 'message' => '', 'data' => []], - 'OK', + (object) ['status' => CommandResponse::STATUS_OK, 'message' => '', 'data' => []], + CommandResponse::STATUS_OK, '', [], ], 'OK response with all fields' => [ - (object) ['status' => 'OK', 'message' => 'Nice!', 'data' => [['foo' => 'bar'], ['baz' => 'bak']]], - 'OK', + (object) [ + 'status' => CommandResponse::STATUS_OK, + 'message' => 'Nice!', + 'data' => [['foo' => 'bar'], ['baz' => 'bak']], + ], + CommandResponse::STATUS_OK, 'Nice!', [['foo' => 'bar'], ['baz' => 'bak']], ], - 'Error response with status and message' => [ - (object) ['status' => 'ERROR', 'message' => 'DuplicateKeyError(Duplicate key error collection)'], - 'ERROR', - 'DuplicateKeyError(Duplicate key error collection)', + 'Invalid error response with status and message' => [ + (object) ['status' => CommandResponse::STATUS_ERROR, 'message' => 'Internal unhandled error'], + CommandResponse::STATUS_ERROR, + 'Internal unhandled error', [], ], ]; } + /** + * @test + * @dataProvider provideResponseStatuses + */ + public function shouldDetectSuccessfulResponse(string $status, bool $shouldBeSuccessful): void + { + $commandResponse = CommandResponse::createFromRawCommandResponseObject((object) ['status' => $status]); + + $this->assertSame($shouldBeSuccessful, $commandResponse->isSuccessful()); + } + + /** + * @return array[] + */ + public function provideResponseStatuses(): array + { + return [ + ['status' => CommandResponse::STATUS_OK, 'isSuccessful' => true], + ['status' => CommandResponse::STATUS_ERROR, 'isSuccessful' => false], + ['status' => CommandResponse::STATUS_INVALID, 'isSuccessful' => false], + ['status' => CommandResponse::STATUS_SKIPPED, 'isSuccessful' => false] , + ]; + } + /** @test */ public function shouldThrowExceptionIfStatusIsMissing(): void { From 1fa4158e3705a4a8324593ac35463b664487d6dc Mon Sep 17 00:00:00 2001 From: Ondrej Machulda Date: Mon, 27 Nov 2017 14:00:32 +0100 Subject: [PATCH 2/2] Add campaign request builder (fixes #38) --- CHANGELOG.md | 1 + README.md | 19 ++++ src/RequestBuilder/CampaignRequestBuilder.php | 67 +++++++++++++ src/RequestBuilder/RequestBuilderFactory.php | 8 ++ .../CampaignRequestBuilderTest.php | 93 +++++++++++++++++++ .../RequestBuilderFactoryTest.php | 6 ++ 6 files changed, 194 insertions(+) create mode 100644 src/RequestBuilder/CampaignRequestBuilder.php create mode 100644 tests/RequestBuilder/CampaignRequestBuilderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ea28ef3..c5e435d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Added - `UserMerge` command and `addUserMerge` & `addUserMerges` method for `EventsRequestBuilder`. (Accessible via `$matej->events()->...`). - `Interaction` command and `addInteraction` & `addInteractions` method for `EventsRequestBuilder`. (Accessible via `$matej->events()->...`). +- `CampaignRequestBuilder` to request batch of recommendations and item sortings for multiple users. (Accessible via `$matej->campaign()->...`). - Method `isSuccessfull()` of `CommandResponse` for easy and encapsulated detection of successful command responses. ### Fixed diff --git a/README.md b/README.md index 8c9dfe3..3df4db2 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,25 @@ $response = $matej->request() ->send(); ``` +### Request batch of recommendations/item sortings + +Use `campaign()` builder to request batch of recommendations or item sorting for multiple users. +Typical use case for this is generating emailing campaigns. + +```php +$matej = new Matej('accountId', 'apikey'); + +$response = $matej->request() + ->campaign() + // Request item sortings + ->addSorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3'])) + ->addSortings([/* array of Sorting objects */]) + // Request user-based recommendations + ->addRecommendation(UserRecommendation::create('user-id', 10, 'emailing', 1.0, 3600)) + ->addRecommendations([/* array of UserRecommendation objects */]) + ->send(); +``` + ## Changelog For latest changes see [CHANGELOG.md](CHANGELOG.md) file. We follow [Semantic Versioning](http://semver.org/). diff --git a/src/RequestBuilder/CampaignRequestBuilder.php b/src/RequestBuilder/CampaignRequestBuilder.php new file mode 100644 index 0000000..a1202d9 --- /dev/null +++ b/src/RequestBuilder/CampaignRequestBuilder.php @@ -0,0 +1,67 @@ +commands[] = $recommendation; + + return $this; + } + + /** + * @param UserRecommendation[] $recommendations + * @return self + */ + public function addRecommendations(array $recommendations): self + { + foreach ($recommendations as $recommendation) { + $this->addRecommendation($recommendation); + } + + return $this; + } + + public function addSorting(Sorting $sorting): self + { + $this->commands[] = $sorting; + + return $this; + } + + /** + * @param Sorting[] $sortings + * @return self + */ + public function addSortings(array $sortings): self + { + foreach ($sortings as $sorting) { + $this->addSorting($sorting); + } + + return $this; + } + + public function build(): Request + { + if (empty($this->commands)) { + throw new LogicException('At least one command must be added to the builder before sending the request'); + } + + return new Request(self::ENDPOINT_PATH, RequestMethodInterface::METHOD_POST, $this->commands); + } +} diff --git a/src/RequestBuilder/RequestBuilderFactory.php b/src/RequestBuilder/RequestBuilderFactory.php index cd1c9c3..552ee5d 100644 --- a/src/RequestBuilder/RequestBuilderFactory.php +++ b/src/RequestBuilder/RequestBuilderFactory.php @@ -46,6 +46,14 @@ public function events(): EventsRequestBuilder return $this->createConfiguredBuilder(EventsRequestBuilder::class); } + /** + * @return CampaignRequestBuilder + */ + public function campaign(): CampaignRequestBuilder + { + return $this->createConfiguredBuilder(CampaignRequestBuilder::class); + } + // TODO: builders for other endpoints /** diff --git a/tests/RequestBuilder/CampaignRequestBuilderTest.php b/tests/RequestBuilder/CampaignRequestBuilderTest.php new file mode 100644 index 0000000..464169b --- /dev/null +++ b/tests/RequestBuilder/CampaignRequestBuilderTest.php @@ -0,0 +1,93 @@ +expectException(LogicException::class); + $this->expectExceptionMessage('At least one command must be added to the builder'); + $builder->build(); + } + + /** @test */ + public function shouldBuildRequestWithCommands(): void + { + $builder = new CampaignRequestBuilder(); + + $recommendationCommand1 = UserRecommendation::create('userId1', 1, 'scenario1', 1.0, 600); + $recommendationCommand2 = UserRecommendation::create('userId2', 2, 'scenario2', 0.5, 700); + $recommendationCommand3 = UserRecommendation::create('userId3', 3, 'scenario3', 0.0, 800); + $builder->addRecommendation($recommendationCommand1); + $builder->addRecommendations([$recommendationCommand2, $recommendationCommand3]); + + $sortingCommand1 = Sorting::create('userId1', ['itemId1', 'itemId2']); + $sortingCommand2 = Sorting::create('userId2', ['itemId2', 'itemId3']); + $sortingCommand3 = Sorting::create('userId3', ['itemId3', 'itemId4']); + + $builder->addSorting($sortingCommand1); + $builder->addSortings([$sortingCommand2, $sortingCommand3]); + + $request = $builder->build(); + + $this->assertInstanceOf(Request::class, $request); + $this->assertSame(RequestMethodInterface::METHOD_POST, $request->getMethod()); + $this->assertSame('/campaign', $request->getPath()); + + $requestData = $request->getData(); + $this->assertCount(6, $requestData); + $this->assertContains($recommendationCommand1, $requestData); + $this->assertContains($recommendationCommand2, $requestData); + $this->assertContains($recommendationCommand3, $requestData); + $this->assertContains($sortingCommand1, $requestData); + $this->assertContains($sortingCommand2, $requestData); + $this->assertContains($sortingCommand3, $requestData); + } + + /** @test */ + public function shouldThrowExceptionWhenSendingCommandsWithoutRequestManager(): void + { + $builder = new CampaignRequestBuilder(); + + $builder->addSorting(Sorting::create('userId1', ['itemId1', 'itemId2'])); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Instance of RequestManager must be set to request builder'); + $builder->send(); + } + + /** @test */ + public function shouldSendRequestViaRequestManager(): void + { + $requestManagerMock = $this->createMock(RequestManager::class); + $requestManagerMock->expects($this->once()) + ->method('sendRequest') + ->with($this->isInstanceOf(Request::class)) + ->willReturn(new Response(0, 0, 0, 0)); + + $builder = new CampaignRequestBuilder(); + $builder->setRequestManager($requestManagerMock); + + $builder->addRecommendation(UserRecommendation::create('userId1', 1, 'scenario1', 1.0, 3600)); + $builder->addSorting(Sorting::create('userId1', ['itemId1', 'itemId2'])); + + $builder->send(); + } +} diff --git a/tests/RequestBuilder/RequestBuilderFactoryTest.php b/tests/RequestBuilder/RequestBuilderFactoryTest.php index e5097ea..d154ecd 100644 --- a/tests/RequestBuilder/RequestBuilderFactoryTest.php +++ b/tests/RequestBuilder/RequestBuilderFactoryTest.php @@ -5,6 +5,7 @@ use Lmc\Matej\Http\RequestManager; use Lmc\Matej\Model\Command\ItemProperty; use Lmc\Matej\Model\Command\ItemPropertySetup; +use Lmc\Matej\Model\Command\Sorting; use Lmc\Matej\Model\Request; use Lmc\Matej\Model\Response; use PHPUnit\Framework\TestCase; @@ -57,10 +58,15 @@ public function provideBuilderMethods(): array $builder->addItemProperty(ItemProperty::create('item-id', [])); }; + $campaignInit = function (CampaignRequestBuilder $builder): void { + $builder->addSorting(Sorting::create('item-id', ['item1', 'item2'])); + }; + return [ ['setupItemProperties', ItemPropertiesSetupRequestBuilder::class, $itemPropertiesSetupInit], ['deleteItemProperties', ItemPropertiesSetupRequestBuilder::class, $itemPropertiesSetupInit], ['events', EventsRequestBuilder::class, $eventInit], + ['campaign', CampaignRequestBuilder::class, $campaignInit], ]; } }