diff --git a/README.md b/README.md index 8ca70c5..92384ab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # PHP API Client for Matej recommendation engine [![Latest Stable Version](https://img.shields.io/packagist/v/lmc/matej-client.svg?style=flat-square)](https://packagist.org/packages/lmc/matej-client) @@ -8,7 +9,7 @@ This library requires PHP 7.1+. However, we provide also PHP 5.6-compatible version [`matej-client-php5`](https://github.com/lmc-eu/matej-client-php5). -Please note the PHP 5.6 version is just transpiled copy of this library - examples, pull requests, issues, changelog etc. are placed in this repository. +Please note that the PHP 5.6 version is just transpiled copy of this library - examples, pull requests, issues, changelog etc. are placed in this repository. ## Installation @@ -41,8 +42,7 @@ $ composer require lmc/matej-client php-http/curl-client guzzlehttp/psr7 # use l ## Usage -To start using Matej you will need your account id (database name) and secret API key - both of them must be obtained -from LMC R&D team. +To start using Matej you will need your account id (database name) and secret API key - both of them must be obtained from LMC R&D team. First create an instance of `Matej` object: ```php @@ -61,7 +61,7 @@ Once finished with building the request, use `send()` method to execute it and r ```php $response = $matej->request() ->events() - ->addInteraction(\Lmc\Matej\Model\Command\Interaction::purchase('user-id', 'item-id')) + ->addInteraction(\Lmc\Matej\Model\Command\Interaction::withItem('purchases', 'user-id', 'item-id')) ->addUserMerge(...) ... ->send(); @@ -155,7 +155,7 @@ $matej = new Matej('accountId', 'apikey'); $response = $matej->request() ->events() // Add interaction between user and item - ->addInteraction(Interaction::purchase('user-id', 'item-id')) + ->addInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) ->addInteractions([/* array of Interaction objects */]) // Update item data ->addItemProperty(ItemProperty::create('item-id', ['valid_from' => time(), 'title' => 'Title'])) @@ -183,7 +183,7 @@ $matej = new Matej('accountId', 'apikey'); $response = $matej->request() ->recommendation(UserRecommendation::create('user-id', 'test-scenario')) - ->setInteraction(Interaction::purchase('user-id', 'item-id')) // optional + ->setInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) // optional ->setUserMerge(UserMerge::mergeInto('user-id', 'source-id')) // optional ->send(); @@ -232,7 +232,7 @@ $recommendations = $response->getRecommendation()->getData(); // } ``` -You can further modify which items will be reccomended by providing boosting rules. Priority of items matching the +You can further modify which items will be recommended by providing boosting rules. Priority of items matching the MQL `$criteria` will be multiplied by the value of `multiplier`: ```php @@ -277,7 +277,7 @@ $recommendedItems = $response->getRecommendation()->getData(); ``` If you don't specify any response properties, Matej will return an array of `stdClass` instances, which contain only `item_id` property. -If you do request at least one response property, you don't need to metion `item_id`, as Matej will always return it regardless of the +If you do request at least one response property, you don't need to mention `item_id`, as Matej will always return it regardless of the properties requested. If you request an unknown property, Matej will return a `BAD REQUEST` with HTTP status code `400`. @@ -296,7 +296,7 @@ $matej = new Matej('accountId', 'apikey'); $response = $matej->request() ->sorting(Sorting::create('user-id', ['item-id-1', 'item-id-2', 'item-id-3'])) - ->setInteraction(Interaction::purchase('user-id', 'item-id')) // optional + ->setInteraction(Interaction::withItem('purchases', 'user-id', 'item-id')) // optional ->setUserMerge(UserMerge::mergeInto('user-id', 'source-id')) // optional ->send(); @@ -342,7 +342,7 @@ $response = $matej->request() ### A/B Testing support `Recommendation` and `Sorting` commands support optional A/B testing of various models. This has to be set up in Matej first, -but once available, you can specify which model you want to use when requesting recommendations or sortings. +but once available, you can specify which model you want to use when requesting recommendations or sorting. This is available for `recommendation`, `sorting` and `campaign` requests: @@ -373,7 +373,7 @@ $response = $matej->request() If you don't provide any model name, the request will be sent without it, and Matej will use default model for your instance. -Typically, you'd select a random sample of users, to which you'd present recommendations and sortings from second model. This way, implementation +Typically, you'd select a random sample of users, to which you'd present recommendations and sorting from second model. This way, implementation in your code should look similar to this: ```php diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 6e5da14..60b3deb 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -1,13 +1,19 @@ + # Upgrading from 2.x to 3.0 -API client release 2.0 contains few backward incompatible changes. +API client release 3.0 contains few backward incompatible changes. This guide will help you upgrade your codebase. -## `UserRecommendation::create()` now accepts only `$user_id` and `$scenario` -`UserReccomentation::create()` accepts only two argumens: `$user_id` and `$scenario`. -Both are arguments are required. Reccomendation command can be further parametrized -using fluent API. +## `UserRecommendation::create()` now accepts only `$userId` and `$scenario` +`UserReccomentation::create()` accepts only two arguments: `$user_id` and `$scenario`. + +Both arguments are required. + +All other arguments can be now set using new setters and are optional: +- `setCount(int $count)` +- `setRotationRate(float $rotationRate)` +- `setRotationTime(int $rotationTime)` #### Before ```php @@ -29,4 +35,76 @@ $recommendation = UserRecommendation::create('user-id', 'scenario'); $recommendation->setCount(5); $recommendation->setRotationRate(1.0); $recommendation->setRotationTime(3600); +``` + +## `Interaction` now accepts interaction type as parameter +Matej now allows configuration of custom interaction types and interaction attributes. + +At the same time, it allows specifying interaction item using item alias instead +of item ID. For that reason we removed static constructor methods for creating specific interaction types - `Interaction::detailView`, `Interaction::purchase`, `Interaction::bookmark` and `Interaction::rating`. + +We replaced them with constructors for creating Interaction based on `$itemId` or `$itemIdAlias`: + +```php +Interaction::withItem( + string $interactionType, + string $userId, + string $itemId, + int $timestamp = null +``` + +```php +Interaction::withAliasedItem( + string $interactionType, + string $userId, + array $itemIdAlias, + int $timestamp = null): +``` + +The first argument is always a string representing interaction type. Consult the table bellow to find out the correct value to fill in: + +| Before: constructor method | After: argument $interactionType | +|------------------------------|----------------------------------| +| `Interaction::detailView` | `"detailviews"` | +| `Interaction::purchase` | `"purchases"` | +| `Interaction::bookmark` | `"bookmarks"` | +| `Interaction::rating` | `"ratings"` | + +> To request new interaction types, please contact Matej support. + +#### Before +```php +$interaction = Interaction::bookmark('user-id', 'item_id', time()); +``` + +#### After +```php +$interaction = Interaction::withItem('bookmarks', 'user-id', 'item_id', time()); +``` +> Argument `$timestamp` remains optional. + +## `Interaction` command supports custom attributes +Interactions now support custom attributes. These can be added using fluent API +methods `setAttribute()` and `setAttributes()`. + +Argument `value` was removed from constructor methods and has to be set using new attribute methods. Its real name might have change as well. For example, for interaction type `bookmarks`, it was renamed to `stars`. + +**Attribute `context` is no longer supported and was removed.** + +#### Before +```php +$interaction = Interaction::rating('user-id', 'item_id', 0.5); +``` + +#### After +```php +$interaction = Interaction::create('ratings', 'user-id', 'item_id') + ->setAttribute('stars', 0.5) +``` + +which is equivalent to + +```php +$interaction = Interaction::create('ratings', 'user-id', 'item_id') +$interaction->setAttribute('stars', 0.5) ``` \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index 75dc23e..2f8ae12 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,8 +4,6 @@ parameters: path: tests/ - message: '#Parameter .+ of class Lmc\\Matej\\Matej constructor expects string#' path: tests/integration/IntegrationTestCase.php - - message: '#Parameter .+ of function forward_static_call_array#' - path: tests/unit/Model/Command/InteractionTest.php - message: '#Parameter \#4 \$body of method Http\\Message\\RequestFactory::createRequest\(\) expects#' path: src/Http/RequestManager.php - '#Unsafe usage of new static\(\)#' diff --git a/src/Model/Command/Constants/InteractionType.php b/src/Model/Command/Constants/InteractionType.php deleted file mode 100644 index 995be47..0000000 --- a/src/Model/Command/Constants/InteractionType.php +++ /dev/null @@ -1,19 +0,0 @@ -interactionType = $interactionType; + $this->attributes = new ArrayObject(); + $this->setInteractionType($interactionType); $this->setUserId($userId); + $this->setItemIdAlias($itemIdAlias); $this->setItemId($itemId); - $this->setValue($value); - $this->setContext($context); $this->setTimestamp($timestamp ?? time()); } /** - * Detail view interaction occurs when a user views an information page with detailed description of given item - * (if there is such a feature available in your system). - * - * @return static + * Construct Interaction between user and item identified by ID. */ - public static function detailView( + public static function withItem( + string $interactionType, string $userId, string $itemId, - float $value = 1.0, - string $context = 'default', int $timestamp = null ): self { - return new static(InteractionType::DETAILVIEWS(), $userId, $itemId, $value, $context, $timestamp); - } + $interaction = new static( + $interactionType, $userId, self::DEFAULT_ITEM_ID_ALIAS, $itemId, $timestamp + ); - /** - * Purchase interaction generally refer to buying or downloading a specific item by a user, suggesting that the user - * believes the item to be of high value for her at the time of purchase. For example in the domain of job boards, - * the purchase interaction stands for a reply of the user on specific Job Description. - * - * @return static - */ - public static function purchase( - string $userId, - string $itemId, - float $value = 1.0, - string $context = 'default', - int $timestamp = null - ): self { - return new static(InteractionType::PURCHASES(), $userId, $itemId, $value, $context, $timestamp); + return $interaction; } /** - * If your applications supports bookmarks, eg. flagging items as favorite, you may submit the interactions as well. - * Depending on the nature of your application, bookmarking an item by a user may mean that the user has found the - * item interesting based on: - * - viewing its details, and has added the item to her future "wishlist", - * - viewing its contents, and would like to view it once more in the future. - * In both cases, bookmarking indicates positive relationship of the user to the item, allowing Matej to refine - * recommendations. - * - * @return static + * Construct Interaction between user and item identified by aliased ID. */ - public static function bookmark( + public static function withAliasedItem( + string $interactionType, string $userId, + string $itemIdAlias, string $itemId, - float $value = 1.0, - string $context = 'default', int $timestamp = null ): self { - return new static(InteractionType::BOOKMARKS(), $userId, $itemId, $value, $context, $timestamp); - } + $interaction = new static( + $interactionType, $userId, $itemIdAlias, $itemId, $timestamp + ); - /** - * Ratings are the most valuable type of interaction user may provide to the Matej recommender – they allow users - * to submit explicit evaluations of items. These may be expressed as a number of stars (1-5), 👍/👎 voting etc. - * For the recommendation API, the ratings must be scaled to real-valued interval [0, 1]. - * - * @return static - */ - public static function rating( - string $userId, - string $itemId, - float $value = 1.0, - string $context = 'default', - int $timestamp = null - ): self { - return new static(InteractionType::RATINGS(), $userId, $itemId, $value, $context, $timestamp); + return $interaction; } public function getUserId(): string @@ -121,16 +84,53 @@ public function getCommandType(): string return 'interaction'; } + /** + * Set all Interaction attributes. All previously set attributes are removed. + */ + public function setAttributes(array $attributes): self + { + $this->attributes = new ArrayObject(); + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + + return $this; + } + + /** + * Set Interaction attribute and its value. If attribute with the same name + * already exists, it's replaced. + * + * @param mixed $value + */ + public function setAttribute(string $name, $value): self + { + Assertion::typeIdentifier($name); + + $this->attributes[$name] = $value; + + return $this; + } + public function getCommandParameters(): array { - return [ - 'interaction_type' => $this->interactionType->jsonSerialize(), + $common = [ + 'interaction_type' => $this->interactionType, 'user_id' => $this->userId, - 'item_id' => $this->itemId, 'timestamp' => $this->timestamp, - 'value' => $this->value, - 'context' => $this->context, + 'attributes' => $this->attributes, + $this->itemIdAlias => $this->itemId, ]; + $common[$this->itemIdAlias] = $this->itemId; + + return $common; + } + + protected function setInteractionType(string $interactionType): void + { + Assertion::typeIdentifier($interactionType); + + $this->interactionType = $interactionType; } protected function setUserId(string $userId): void @@ -147,18 +147,11 @@ protected function setItemId(string $itemId): void $this->itemId = $itemId; } - protected function setValue(float $value): void - { - Assertion::between($value, 0, 1); - - $this->value = $value; - } - - protected function setContext(string $context): void + protected function setItemIdAlias(string $itemIdAlias): void { - Assertion::typeIdentifier($context); + Assertion::typeIdentifier($itemIdAlias); - $this->context = $context; + $this->itemIdAlias = $itemIdAlias; } protected function setTimestamp(int $timestamp): void diff --git a/tests/integration/RequestBuilder/EventsRequestBuilderTest.php b/tests/integration/RequestBuilder/EventsRequestBuilderTest.php index 1270c4a..88a5f4e 100644 --- a/tests/integration/RequestBuilder/EventsRequestBuilderTest.php +++ b/tests/integration/RequestBuilder/EventsRequestBuilderTest.php @@ -51,11 +51,10 @@ public function shouldExecuteInteractionAndUserMergeAndItemPropertyCommands(): v $response = static::createMatejInstance() ->request() ->events() - ->addInteraction(Interaction::bookmark('user-a', 'item-a')) + ->addInteraction(Interaction::withItem('search', 'user-a', 'item-a')) ->addInteractions([ - Interaction::detailView('user-b', 'item-a'), - Interaction::rating('user-c', 'item-a'), - Interaction::purchase('user-d', 'item-a'), + Interaction::withItem('detailviews', 'user-b', 'item-a'), + Interaction::withItem('purchases', 'user-d', 'item-a'), ]) ->addUserMerge(UserMerge::mergeInto('user-a', 'user-b')) ->addUserMerges([ @@ -69,7 +68,7 @@ public function shouldExecuteInteractionAndUserMergeAndItemPropertyCommands(): v ]) ->send(); - $this->assertResponseCommandStatuses($response, ...$this->generateOkStatuses(10)); + $this->assertResponseCommandStatuses($response, ...$this->generateOkStatuses(9)); } private static function addPropertiesToPropertySetupRequest(ItemPropertiesSetupRequestBuilder $builder): void diff --git a/tests/integration/RequestBuilder/InteractionRequestTest.php b/tests/integration/RequestBuilder/InteractionRequestTest.php new file mode 100644 index 0000000..66e5e96 --- /dev/null +++ b/tests/integration/RequestBuilder/InteractionRequestTest.php @@ -0,0 +1,71 @@ +request() + ->events() + ->addInteraction(Interaction::withItem('invalid-type', 'user-a', 'item-a')) + ->send(); + + $this->assertResponseCommandStatuses($response, 'INVALID'); + } + + /** @test */ + public function shouldSendInteractionWithCustomAttribute(): void + { + $response = static::createMatejInstance() + ->request() + ->events() + ->addInteraction(Interaction::withItem('purchases', 'user-a', 'item-a') + ->setAttribute('quantity', 2) + ) + ->send(); + + $this->assertResponseCommandStatuses($response, 'OK'); + } + + /** @test */ + public function shouldSendInteractionWithCustomAttributes(): void + { + $response = static::createMatejInstance() + ->request() + ->events() + ->addInteraction( + Interaction::withAliasedItem('search', 'user-a', 'search_id', 'search-id') + ->setAttributes([ + 'query' => 'query value', + 'keywords' => 'key, words', + 'serp' => ['serp1', 'serp2'], + 'categories' => ['category1', 'category2'], + 'location' => [['lat' => 41.2395, 'long' => 3.40592]], + 'is_logged_in' => true, + ]) + ) + ->send(); + $this->assertResponseCommandStatuses($response, 'OK'); + } + + /** @test */ + public function shouldSendInteractionWithItemAlias(): void + { + $response = static::createMatejInstance() + ->request() + ->events() + ->addInteraction(Interaction::withAliasedItem('search', 'user-a', 'search_id', 'search-id')) + ->send(); + + $this->assertResponseCommandStatuses($response, 'OK'); + } +} diff --git a/tests/integration/RequestBuilder/RecommendationRequestBuilderTest.php b/tests/integration/RequestBuilder/RecommendationRequestBuilderTest.php index feec868..271528e 100644 --- a/tests/integration/RequestBuilder/RecommendationRequestBuilderTest.php +++ b/tests/integration/RequestBuilder/RecommendationRequestBuilderTest.php @@ -22,7 +22,7 @@ public function shouldExecuteRecommendationRequestOnly(): void $response = static::createMatejInstance() ->request() ->recommendation($this->createRecommendationCommand('user-a') - ->addBoost(Boost::create('test', 1.2)) + ->addBoost(Boost::create('age > 1', 1.2)) )->send(); $this->assertInstanceOf(RecommendationsResponse::class, $response); @@ -37,9 +37,10 @@ public function shouldExecuteRecommendationRequestWithUserMergeAndInteraction(): ->request() ->recommendation($this->createRecommendationCommand('user-b')) ->setUserMerge(UserMerge::mergeInto('user-b', 'user-a')) - ->setInteraction(Interaction::bookmark('user-a', 'item-a')) + ->setInteraction( + Interaction::withItem('detailviews', 'user-a', 'item-a') + ) ->send(); - $this->assertInstanceOf(RecommendationsResponse::class, $response); $this->assertResponseCommandStatuses($response, 'OK', 'OK', 'OK'); $this->assertShorthandResponse($response, 'OK', 'OK', 'OK'); diff --git a/tests/integration/RequestBuilder/SortingRequestTest.php b/tests/integration/RequestBuilder/SortingRequestTest.php index 1ac8694..deb3672 100644 --- a/tests/integration/RequestBuilder/SortingRequestTest.php +++ b/tests/integration/RequestBuilder/SortingRequestTest.php @@ -35,7 +35,7 @@ public function shouldExecuteSortingRequestWithUserMergeAndInteraction(): void ->request() ->sorting(Sorting::create('user-b', ['item-a', 'item-b', 'itemC-c'])) ->setUserMerge(UserMerge::mergeInto('user-b', 'user-a')) - ->setInteraction(Interaction::bookmark('user-a', 'item-a')) + ->setInteraction(Interaction::withItem('detailviews', 'user-a', 'item-a')) ->send(); $this->assertInstanceOf(SortingResponse::class, $response); diff --git a/tests/unit/Model/Command/InteractionTest.php b/tests/unit/Model/Command/InteractionTest.php index 2b6868f..d34988e 100644 --- a/tests/unit/Model/Command/InteractionTest.php +++ b/tests/unit/Model/Command/InteractionTest.php @@ -2,6 +2,8 @@ namespace Lmc\Matej\Model\Command; +use ArrayObject; +use Lmc\Matej\Exception\DomainException; use phpmock\phpunit\PHPMock; use PHPUnit\Framework\TestCase; @@ -20,54 +22,192 @@ public function initTimeMock(): void /** * @test - * @dataProvider provideConstructorName + */ + public function shouldRaiseExceptionWithInvalidAttributeName(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withItem('bookmarks', 'user-id', 'item-id'); + $command->setAttribute('invalid^*!@', 'value'); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithInvalidAttributeNameInBatch(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withItem('bookmarks', 'user-id', 'item-id'); + $command->setAttributes([ + 'valid' => 'value1', + 'invalid^*!@' => 'value2', + ]); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithInvalidInteractionType(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withItem('invalid^*!@', 'user-id', 'item-id'); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithInvalidUserId(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withItem('bookmarks', 'invalid^*!@', 'item-id'); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithInvalidItemId(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withItem('bookmarks', 'user-id', 'invalid^*!@'); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithInvalidItemIdAliasName(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage( + 'Value "invalid^*!@" does not match type identifier format requirement (must contain only of alphanumeric chars, dash or underscore)'); + $command = Interaction::withAliasedItem('bookmarks', 'user-id', 'invalid^*!@', 'item-id'); + } + + /** + * @test + */ + public function shouldRaiseExceptionWithNegativeTimestamp(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage('Provided "-123" is not greater than "0".'); + $command = Interaction::withItem('bookmarks', 'user-id', 'item-id', -123); + } + + /** + * @test + */ + public function shouldCompileInteractionAttributes(): void + { + $command = Interaction::withItem('bookmarks', 'user-id', 'item-id'); + $command->setAttribute('attribute-1', 'value-1'); + $command->setAttributes([ + 'attribute-2' => 'value-2', + 'attribute-3' => 'value-3', + ]); + $command->setAttribute('attribute-2', 'value-new-2'); + + $this->assertEquals( + $command->jsonSerialize()['parameters']['attributes'], + new ArrayObject([ + 'attribute-2' => 'value-new-2', + 'attribute-3' => 'value-3', + ]) + ); + } + + /** + * @test + * @dataProvider provideItemIdConstructorParams * @runInSeparateProcess so that time() can be mocked safely */ - public function shouldBeInstantiableViaNamedConstructors( - string $constructorName, - string $expectedInteractionType, - array $extraConstructorParams - ): void { - $constructorParams = array_merge(['exampleUserId', 'exampleItemId'], $extraConstructorParams); + public function shouldBeInstantiableWithItemId(array $constructorParams): void + { + /** @var Interaction $command */ + $command = forward_static_call_array( + [Interaction::class, 'withItem'], $constructorParams + ); + + $this->assertInstanceOf(Interaction::class, $command); + $this->assertEquals( + [ + 'type' => 'interaction', + 'parameters' => [ + 'interaction_type' => $constructorParams[0], + 'user_id' => $constructorParams[1], + 'item_id' => $constructorParams[2], + 'timestamp' => $constructorParams[3] ?? static::TIMESTAMP, + 'attributes' => new ArrayObject(), + ], + ], + $command->jsonSerialize() + ); + $this->assertSame($constructorParams[1], $command->getUserId()); + } + /** + * @test + * @dataProvider provideItemIdAliasConstructorParams + * @runInSeparateProcess so that time() can be mocked safely + */ + public function shouldBeInstantiableWithItemIdAlias(array $constructorParams): void + { /** @var Interaction $command */ $command = forward_static_call_array( - [Interaction::class, $constructorName], - $constructorParams + [Interaction::class, 'withAliasedItem'], $constructorParams ); $this->assertInstanceOf(Interaction::class, $command); - $this->assertSame( + $this->assertEquals( [ 'type' => 'interaction', 'parameters' => [ - 'interaction_type' => $expectedInteractionType, - 'user_id' => 'exampleUserId', - 'item_id' => 'exampleItemId', - 'timestamp' => $extraConstructorParams[2] ?? static::TIMESTAMP, - 'value' => $extraConstructorParams[0] ?? 1.0, - 'context' => $extraConstructorParams[1] ?? 'default', + 'interaction_type' => $constructorParams[0], + 'user_id' => $constructorParams[1], + 'timestamp' => $constructorParams[4] ?? static::TIMESTAMP, + 'attributes' => new ArrayObject(), + $constructorParams[2] => $constructorParams[3], ], ], $command->jsonSerialize() ); - $this->assertSame('exampleUserId', $command->getUserId()); + $this->assertSame($constructorParams[1], $command->getUserId()); + } + + /** + * @return array[] + */ + public function provideItemIdConstructorParams(): array + { + return [ + 'with item_id and required params' => [ + ['bookmarks', 'user123', 'item123'], + ], + 'with item_id and optional params' => [ + ['bookmarks', 'user123', 'item123', 123], + ], + ]; } /** * @return array[] */ - public function provideConstructorName(): array + public function provideItemIdAliasConstructorParams(): array { return [ - 'detailView with only required params' => ['detailView', 'detailviews', []], - 'detailView with optional params' => ['detailView', 'detailviews', [0.5, 'myContextFoo', 1337333666]], - 'purchase with only required params' => ['purchase', 'purchases', []], - 'purchase with optional params' => ['purchase', 'purchases', [0.0, 'myContextBar', 1337333666]], - 'bookmark with only required params' => ['bookmark', 'bookmarks', []], - 'bookmark with optional params' => ['bookmark', 'bookmarks', [0.1337, 'myContextBaz', 1337333666]], - 'rating with only required params' => ['rating', 'ratings', []], - 'rating with optional params' => ['rating', 'ratings', [0.9, 'myContextBan', 1337333666]], + 'with single item_id_alias and required params' => [ + ['detailviews', 'user123', 'key', 'value'], + ], + 'with single item_id_alias and optional params' => [ + ['bookmarks', 'user123', 'key', 'value', 123], + ], ]; } } diff --git a/tests/unit/RequestBuilder/EventsRequestBuilderTest.php b/tests/unit/RequestBuilder/EventsRequestBuilderTest.php index c3b6dbd..3d321bd 100644 --- a/tests/unit/RequestBuilder/EventsRequestBuilderTest.php +++ b/tests/unit/RequestBuilder/EventsRequestBuilderTest.php @@ -24,9 +24,9 @@ public function shouldBuildRequestWithCommands(): void { $builder = new EventsRequestBuilder(); - $interactionCommand1 = Interaction::detailView('userId1', 'itemId1'); - $interactionCommand2 = Interaction::bookmark('userId1', 'itemId1'); - $interactionCommand3 = Interaction::purchase('userId1', 'itemId1'); + $interactionCommand1 = Interaction::withItem('detailviews', 'userId1', 'itemId1'); + $interactionCommand2 = Interaction::withItem('bookmarks', 'userId1', 'itemId1'); + $interactionCommand3 = Interaction::withItem('purchases', 'userId1', 'itemId1'); $builder->addInteraction($interactionCommand1); $builder->addInteractions([$interactionCommand2, $interactionCommand3]); @@ -81,7 +81,7 @@ public function shouldThrowExceptionWhenBatchSizeIsTooBig(): void $builder = new EventsRequestBuilder(); for ($i = 0; $i < 334; $i++) { - $builder->addInteraction(Interaction::detailView('userId1', 'itemId1')); + $builder->addInteraction(Interaction::withItem('detailview', 'userId1', 'itemId1')); $builder->addItemProperty(ItemProperty::create('itemId1', ['key1' => 'value1'])); $builder->addUserMerge(UserMerge::mergeFromSourceToTargetUser('sourceId1', 'targetId1')); } diff --git a/tests/unit/RequestBuilder/RecommendationRequestBuilderTest.php b/tests/unit/RequestBuilder/RecommendationRequestBuilderTest.php index b1094cb..da9f39d 100644 --- a/tests/unit/RequestBuilder/RecommendationRequestBuilderTest.php +++ b/tests/unit/RequestBuilder/RecommendationRequestBuilderTest.php @@ -29,7 +29,7 @@ public function shouldBuildRequestWithCommands(): void ->setRotationTime(3600); $builder = new RecommendationRequestBuilder($recommendationsCommand); - $interactionCommand = Interaction::detailView('sourceId1', 'itemId1'); + $interactionCommand = Interaction::withItem('detailviews', 'sourceId1', 'itemId1'); $builder->setInteraction($interactionCommand); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser('sourceId1', 'userId1'); @@ -91,7 +91,7 @@ public function shouldThrowExceptionWhenInteractionIsForUnrelatedUser(): void ->setRotationTime(3600) ); - $builder->setInteraction(Interaction::purchase('different-user', 'itemId1')); + $builder->setInteraction(Interaction::withItem('purchases', 'different-user', 'itemId1')); $this->expectException(LogicException::class); $this->expectExceptionMessage( @@ -133,7 +133,7 @@ public function shouldPassOnCorrectSequenceOfUsersWhenMerging( string $targetUserId, string $recommendationUser ): void { - $interactionCommand = Interaction::purchase($interactionUser, 'test-item-id'); + $interactionCommand = Interaction::withItem('purchases', $interactionUser, 'test-item-id'); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser($sourceUserToBeDeleted, $targetUserId); $recommendationsCommand = UserRecommendation::create($recommendationUser, 'scenario') ->setCount(5) @@ -153,7 +153,7 @@ public function shouldPassOnCorrectSequenceOfUsersWhenMerging( */ public function shouldFailOnIncorrectSequenceOfUsersWhenMerging(): void { - $interactionCommand = Interaction::purchase('test-user-a', 'test-item-id'); + $interactionCommand = Interaction::withItem('purchases', 'test-user-a', 'test-item-id'); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser('test-user-b', 'test-user-a'); $recommendationsCommand = UserRecommendation::create('test-user-b', 'scenario') ->setCount(5) diff --git a/tests/unit/RequestBuilder/SortingRequestBuilderTest.php b/tests/unit/RequestBuilder/SortingRequestBuilderTest.php index 74b49ae..b714d39 100644 --- a/tests/unit/RequestBuilder/SortingRequestBuilderTest.php +++ b/tests/unit/RequestBuilder/SortingRequestBuilderTest.php @@ -26,7 +26,7 @@ public function shouldBuildRequestWithCommands(): void $sortingCommand = Sorting::create('userId1', ['itemId1', 'itemId2']); $builder = new SortingRequestBuilder($sortingCommand); - $interactionCommand = Interaction::detailView('sourceId1', 'itemId1'); + $interactionCommand = Interaction::withItem('detailviews', 'sourceId1', 'itemId1'); $builder->setInteraction($interactionCommand); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser('sourceId1', 'userId1'); @@ -79,7 +79,7 @@ public function shouldThrowExceptionWhenUserOfInteractionDiffersFromSorting(): v { $builder = new SortingRequestBuilder(Sorting::create('userId1', ['itemId1', 'itemId2'])); - $builder->setInteraction(Interaction::purchase('different-user', 'itemId1')); + $builder->setInteraction(Interaction::withItem('purchases', 'different-user', 'itemId1')); $this->expectException(LogicException::class); $this->expectExceptionMessage( @@ -109,7 +109,7 @@ public function shouldThrowExceptionWhenUserOfUserMergeDiffersFromSorting(): voi */ public function shouldPassOnCorrectSequenceOfUsersWhenMerging(): void { - $interactionCommand = Interaction::purchase('test-user-a', 'test-item-id'); + $interactionCommand = Interaction::withItem('purchase', 'test-user-a', 'test-item-id'); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser('test-user-a', 'test-user-b'); $sortingCommand = Sorting::create('test-user-b', ['itemId1', 'itemId2']); @@ -126,7 +126,7 @@ public function shouldPassOnCorrectSequenceOfUsersWhenMerging(): void */ public function shouldFailOnIncorrectSequenceOfUsersWhenMerging(): void { - $interactionCommand = Interaction::purchase('test-user-a', 'test-item-id'); + $interactionCommand = Interaction::withItem('purchase', 'test-user-a', 'test-item-id'); $userMergeCommand = UserMerge::mergeFromSourceToTargetUser('test-user-b', 'test-user-a'); $sortingCommand = Sorting::create('test-user-a', ['itemId1', 'itemId2']);