From feff8479e2328969a9cc475674fe6a6234be253a Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:24:26 +0300 Subject: [PATCH 1/6] feat(test): add utility for checking to skip old synonym tests --- tests/TestCase.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/TestCase.php b/tests/TestCase.php index 6a26447b..b9852b11 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,7 @@ use Typesense\Client; use Mockery; use Typesense\ApiCall; +use Exception; abstract class TestCase extends BaseTestCase { @@ -98,4 +99,25 @@ protected function tearDownTypesense(): void $this->typesenseClient->collections[$collection['name']]->delete(); } } + + protected function isV30OrAbove(): bool + { + try { + $debug = $this->typesenseClient->debug->retrieve(); + $version = $debug['version']; + + if ($version === 'nightly') { + return true; + } + + if (preg_match('/^v(\d+)/', $version, $matches)) { + $majorVersion = (int) $matches[1]; + return $majorVersion >= 30; + } + + return false; + } catch (Exception $e) { + return false; + } + } } From ef83c3732240df936f6d5ca1440f9f779c146a5f Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Tue, 19 Aug 2025 18:24:58 +0300 Subject: [PATCH 2/6] feat(test): skip deprecated API tests for Synonyms and Analytics --- tests/Feature/AnalyticsEventsTest.php | 14 +++++++++++++- tests/Feature/AnalyticsRulesTest.php | 19 ++++++++++++++++--- tests/Feature/SynonymsTest.php | 5 +++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/tests/Feature/AnalyticsEventsTest.php b/tests/Feature/AnalyticsEventsTest.php index c1fa70d6..6b9a433c 100644 --- a/tests/Feature/AnalyticsEventsTest.php +++ b/tests/Feature/AnalyticsEventsTest.php @@ -3,6 +3,7 @@ namespace Feature; use Tests\TestCase; +use Exception; class AnalyticsEventsTest extends TestCase { @@ -11,6 +12,11 @@ class AnalyticsEventsTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + $this->client()->collections->create([ "name" => "products", "fields" => [ @@ -52,7 +58,13 @@ protected function setUp(): void protected function tearDown(): void { parent::tearDown(); - $this->client()->analytics->rules()->{'product_queries_aggregation'}->delete(); + + if (!$this->isV30OrAbove()) { + try { + $this->client()->analytics->rules()->{'product_queries_aggregation'}->delete(); + } catch (Exception $e) { + } + } } public function testCanCreateAnEvent(): void diff --git a/tests/Feature/AnalyticsRulesTest.php b/tests/Feature/AnalyticsRulesTest.php index 882ef896..e5d84ead 100644 --- a/tests/Feature/AnalyticsRulesTest.php +++ b/tests/Feature/AnalyticsRulesTest.php @@ -4,6 +4,7 @@ use Tests\TestCase; use Typesense\Exceptions\ObjectNotFound; +use Exception; class AnalyticsRulesTest extends TestCase { @@ -26,14 +27,26 @@ class AnalyticsRulesTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + $this->ruleUpsertResponse = $this->client()->analytics->rules()->upsert($this->ruleName, $this->ruleConfiguration); } protected function tearDown(): void { - $rules = $this->client()->analytics->rules()->retrieve(); - foreach ($rules['rules'] as $rule) { - $this->client()->analytics->rules()->{$rule['name']}->delete(); + if (!$this->isV30OrAbove()) { + try { + $rules = $this->client()->analytics->rules()->retrieve(); + if (is_array($rules) && isset($rules['rules'])) { + foreach ($rules['rules'] as $rule) { + $this->client()->analytics->rules()->{$rule['name']}->delete(); + } + } + } catch (Exception $e) { + } } } diff --git a/tests/Feature/SynonymsTest.php b/tests/Feature/SynonymsTest.php index 6cd5db05..5a61b871 100644 --- a/tests/Feature/SynonymsTest.php +++ b/tests/Feature/SynonymsTest.php @@ -17,6 +17,11 @@ class SynonymsTest extends TestCase protected function setUp(): void { parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Synonyms is deprecated in Typesense v30+, use SynonymSets instead'); + } + $this->setUpCollection('books'); $this->synonyms = $this->client()->collections['books']->synonyms; From 06ff1c97ff7c412d73fbefb59f4cc1908c492623 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 20 Aug 2025 14:53:43 +0300 Subject: [PATCH 3/6] feat(analytics): add analytics v2 classes --- src/AnalyticsEventsV2.php | 45 +++++++++++++++++++++++++ src/AnalyticsRuleV2.php | 51 ++++++++++++++++++++++++++++ src/AnalyticsRulesV2.php | 70 +++++++++++++++++++++++++++++++++++++++ src/AnalyticsV2.php | 35 ++++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 src/AnalyticsEventsV2.php create mode 100644 src/AnalyticsRuleV2.php create mode 100644 src/AnalyticsRulesV2.php create mode 100644 src/AnalyticsV2.php diff --git a/src/AnalyticsEventsV2.php b/src/AnalyticsEventsV2.php new file mode 100644 index 00000000..ae9f4c6b --- /dev/null +++ b/src/AnalyticsEventsV2.php @@ -0,0 +1,45 @@ +apiCall = $apiCall; + } + + /** + * Create an analytics event + * + * @param array $params Event parameters including name, event_type, and data + * @return array Response from the API + * @throws TypesenseClientError|HttpClientException + */ + public function create(array $params) + { + return $this->apiCall->post(self::RESOURCE_PATH, $params); + } + + /** + * Retrieve analytics events + * + * @param array $params Query parameters + * @return array Response from the API + */ + public function retrieve(array $params = []) + { + return $this->apiCall->get(self::RESOURCE_PATH, $params); + } +} \ No newline at end of file diff --git a/src/AnalyticsRuleV2.php b/src/AnalyticsRuleV2.php new file mode 100644 index 00000000..270bb688 --- /dev/null +++ b/src/AnalyticsRuleV2.php @@ -0,0 +1,51 @@ +ruleName = $ruleName; + $this->apiCall = $apiCall; + } + + /** + * Retrieve a specific analytics rule + * + * @return array Response from the API + */ + public function retrieve() + { + return $this->apiCall->get($this->endpointPath(), []); + } + + /** + * Delete a specific analytics rule + * + * @return array Response from the API + */ + public function delete() + { + return $this->apiCall->delete($this->endpointPath()); + } + + /** + * Update a specific analytics rule + * + * @param array $params Rule parameters + * @return array Response from the API + */ + public function update(array $params) + { + return $this->apiCall->put($this->endpointPath(), $params); + } + + private function endpointPath() + { + return AnalyticsRulesV2::RESOURCE_PATH . '/' . encodeURIComponent($this->ruleName); + } +} \ No newline at end of file diff --git a/src/AnalyticsRulesV2.php b/src/AnalyticsRulesV2.php new file mode 100644 index 00000000..39639249 --- /dev/null +++ b/src/AnalyticsRulesV2.php @@ -0,0 +1,70 @@ +apiCall = $apiCall; + } + + /** + * Create multiple analytics rules + * + * @param array $rules Array of rule objects + * @return array Response from the API + */ + public function create(array $rules) + { + return $this->apiCall->post(self::RESOURCE_PATH, $rules); + } + + /** + * Retrieve all analytics rules + * + * @return array Response from the API + */ + public function retrieve() + { + return $this->apiCall->get(self::RESOURCE_PATH, []); + } + + /** + * Get a specific rule by name + * + * @param string $ruleName + * @return AnalyticsRuleV2 + */ + public function __get($ruleName) + { + return new AnalyticsRuleV2($ruleName, $this->apiCall); + } + + /** + * ArrayAccess implementation for backwards compatibility + */ + public function offsetExists($offset): bool + { + return true; // Rules can be accessed by name + } + + public function offsetGet($offset): AnalyticsRuleV2 + { + return new AnalyticsRuleV2($offset, $this->apiCall); + } + + public function offsetSet($offset, $value): void + { + // Not implemented for read-only access + } + + public function offsetUnset($offset): void + { + // Not implemented for read-only access + } +} \ No newline at end of file diff --git a/src/AnalyticsV2.php b/src/AnalyticsV2.php new file mode 100644 index 00000000..1a3629dc --- /dev/null +++ b/src/AnalyticsV2.php @@ -0,0 +1,35 @@ +apiCall = $apiCall; + } + + public function rules() + { + if (!isset($this->rules)) { + $this->rules = new AnalyticsRulesV2($this->apiCall); + } + return $this->rules; + } + + public function events() + { + if (!isset($this->events)) { + $this->events = new AnalyticsEventsV2($this->apiCall); + } + return $this->events; + } +} \ No newline at end of file From 438f06cfc4549210cc04326ed3134e24e687e3c3 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 20 Aug 2025 14:53:56 +0300 Subject: [PATCH 4/6] feat(client): register analytics v2 to client object --- src/Client.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Client.php b/src/Client.php index a4f8a084..d0cb7b81 100644 --- a/src/Client.php +++ b/src/Client.php @@ -75,6 +75,11 @@ class Client */ public Analytics $analytics; + /** + * @var AnalyticsV2 + */ + public AnalyticsV2 $analyticsV2; + /** * @var Stemming */ @@ -118,6 +123,7 @@ public function __construct(array $config) $this->multiSearch = new MultiSearch($this->apiCall); $this->presets = new Presets($this->apiCall); $this->analytics = new Analytics($this->apiCall); + $this->analyticsV2 = new AnalyticsV2($this->apiCall); $this->stemming = new Stemming($this->apiCall); $this->conversations = new Conversations($this->apiCall); $this->nlSearchModels = new NLSearchModels($this->apiCall); From 38d6765de32dfd22a9b25fd93aa055d3c84db9b1 Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 20 Aug 2025 14:54:13 +0300 Subject: [PATCH 5/6] test: add test suite for analytics v2 --- tests/Feature/AnalyticsEventsV2Test.php | 168 +++++++++++++++++++++++ tests/Feature/AnalyticsRulesV2Test.php | 175 ++++++++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 tests/Feature/AnalyticsEventsV2Test.php create mode 100644 tests/Feature/AnalyticsRulesV2Test.php diff --git a/tests/Feature/AnalyticsEventsV2Test.php b/tests/Feature/AnalyticsEventsV2Test.php new file mode 100644 index 00000000..f6cdb990 --- /dev/null +++ b/tests/Feature/AnalyticsEventsV2Test.php @@ -0,0 +1,168 @@ +ruleConfiguration = [ + "name" => $this->ruleName, + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 1 + ] + ]; + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('New Analytics API is not supported in Typesense v29.0 and below'); + } + + try { + $this->client()->collections->create([ + 'name' => 'test_products', + 'fields' => [ + ['name' => 'company_name', 'type' => 'string'], + ['name' => 'num_employees', 'type' => 'int32'], + ['name' => 'country', 'type' => 'string', 'facet' => true], + ['name' => 'popularity', 'type' => 'int32', 'optional' => true] + ], + 'default_sorting_field' => 'num_employees' + ]); + } catch (Exception $e) { + } + + try { + $this->client()->analyticsV2->rules()->create([$this->ruleConfiguration]); + } catch (Exception $e) { + } + } + + protected function tearDown(): void + { + if (!$this->isV30OrAbove()) { + try { + $rules = $this->client()->analyticsV2->rules()->retrieve(); + if (is_array($rules)) { + foreach ($rules as $rule) { + if (strpos($rule['name'], 'test_v2_') === 0) { + try { + $this->client()->analyticsV2->rules()[$rule['name']]->delete(); + } catch (Exception $e) { + } + } + } + } + } catch (Exception $e) { + } + + try { + $this->client()->collections['test_products']->delete(); + } catch (Exception $e) { + } + } + } + + public function testCanCreateEventsWithV2API(): void + { + $event = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_ids" => ["1", "2"], + "user_id" => "test_user" + ] + ]; + + $response = $this->client()->analyticsV2->events()->create($event); + $this->assertIsArray($response); + } + + public function testCanCreateMultipleEventsWithV2API(): void + { + $event1 = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user_1" + ] + ]; + + $event2 = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "2", + "user_id" => "test_user_2" + ] + ]; + + $response1 = $this->client()->analyticsV2->events()->create($event1); + $this->assertIsArray($response1); + + $response2 = $this->client()->analyticsV2->events()->create($event2); + $this->assertIsArray($response2); + } + + public function testCanRetrieveEventsWithV2API(): void + { + $event = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user" + ] + ]; + + $this->client()->analyticsV2->events()->create($event); + + $response = $this->client()->analyticsV2->events()->retrieve([ + 'user_id' => 'test_user', + 'name' => $this->ruleName, + 'n'=> 10 + ]); + + $this->assertIsArray($response); + } + + public function testCanCreateEventWithDifferentEventTypes(): void + { + $clickEvent = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user" + ] + ]; + + $conversionEvent = [ + "name" => $this->ruleName, + "event_type" => "conversion", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user" + ] + ]; + + $clickResponse = $this->client()->analyticsV2->events()->create($clickEvent); + $this->assertIsArray($clickResponse); + + $conversionResponse = $this->client()->analyticsV2->events()->create($conversionEvent); + $this->assertIsArray($conversionResponse); + } +} \ No newline at end of file diff --git a/tests/Feature/AnalyticsRulesV2Test.php b/tests/Feature/AnalyticsRulesV2Test.php new file mode 100644 index 00000000..ec3b5b32 --- /dev/null +++ b/tests/Feature/AnalyticsRulesV2Test.php @@ -0,0 +1,175 @@ +ruleConfiguration = [ + "name" => $this->ruleName, + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 1 + ] + ]; + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('New Analytics API is not supported in Typesense v29.0 and below'); + } + + try { + $this->client()->collections->create([ + 'name' => 'test_products', + 'fields' => [ + ['name' => 'company_name', 'type' => 'string'], + ['name' => 'num_employees', 'type' => 'int32'], + ['name' => 'country', 'type' => 'string', 'facet' => true], + ['name' => 'popularity', 'type' => 'int32', 'optional' => true] + ], + 'default_sorting_field' => 'num_employees' + ]); + } catch (Exception $e) { + } + + try { + $this->client()->analyticsV2->rules()->create([$this->ruleConfiguration]); + } catch (Exception $e) { + } + } + + protected function tearDown(): void + { + if (!$this->isV30OrAbove()) { + try { + $rules = $this->client()->analyticsV2->rules()->retrieve(); + if (is_array($rules)) { + foreach ($rules as $rule) { + if (strpos($rule['name'], 'test_v2_') === 0) { + try { + $this->client()->analyticsV2->rules()[$rule['name']]->delete(); + } catch (Exception $e) { + } + } + } + } + } catch (Exception $e) { + } + + try { + $this->client()->collections['test_products']->delete(); + } catch (Exception $e) { + } + } + } + + public function testCanCreateRulesWithV2API(): void + { + $rules = [ + [ + "name" => "test_rule_1", + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 1 + ] + ], + [ + "name" => "test_rule_2", + "type" => "counter", + "collection" => "test_products", + "event_type" => "conversion", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 2 + ] + ] + ]; + + $response = $this->client()->analyticsV2->rules()->create($rules); + $this->assertIsArray($response); + + $allRules = $this->client()->analyticsV2->rules()->retrieve(); + $this->assertIsArray($allRules); + + $ruleNames = array_column($allRules, 'name'); + $this->assertContains('test_rule_1', $ruleNames); + $this->assertContains('test_rule_2', $ruleNames); + } + + public function testCanRetrieveARuleWithV2API(): void + { + $returnData = $this->client()->analyticsV2->rules()[$this->ruleName]->retrieve(); + $this->assertEquals($this->ruleName, $returnData['name']); + $this->assertEquals('counter', $returnData['type']); + $this->assertEquals('test_products', $returnData['collection']); + } + + public function testCanUpdateARuleWithV2API(): void + { + $updateParams = [ + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "updated_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 5 + ] + ]; + + $response = $this->client()->analyticsV2->rules()[$this->ruleName]->update($updateParams); + $this->assertEquals($this->ruleName, $response['name']); + $this->assertEquals('updated_tag', $response['rule_tag']); + $this->assertEquals(5, $response['params']['weight']); + } + + public function testCanDeleteARuleWithV2API(): void + { + $returnData = $this->client()->analyticsV2->rules()[$this->ruleName]->delete(); + $this->assertEquals($this->ruleName, $returnData['name']); + + $this->expectException(RequestMalformed::class); + $this->client()->analyticsV2->rules()[$this->ruleName]->retrieve(); + } + + public function testCanRetrieveAllRulesWithV2API(): void + { + $returnData = $this->client()->analyticsV2->rules()->retrieve(); + $this->assertIsArray($returnData); + $this->assertGreaterThanOrEqual(1, count($returnData)); + + $ruleNames = array_column($returnData, 'name'); + $this->assertContains('test_v2_rule', $ruleNames); + $this->assertContains('test_rule_1', $ruleNames); + $this->assertContains('test_rule_2', $ruleNames); + } + + public function testArrayAccessCompatibility(): void + { + $rule = $this->client()->analyticsV2->rules()[$this->ruleName]; + $this->assertInstanceOf('Typesense\AnalyticsRuleV2', $rule); + + $this->assertTrue(isset($this->client()->analyticsV2->rules()[$this->ruleName])); + + $rule = $this->client()->analyticsV2->rules()[$this->ruleName]; + $this->assertInstanceOf('Typesense\AnalyticsRuleV2', $rule); + } +} \ No newline at end of file From ab74432b12a700591be8eff26d24bde2bc55f5ef Mon Sep 17 00:00:00 2001 From: Fanis Tharropoulos Date: Wed, 20 Aug 2025 15:07:55 +0300 Subject: [PATCH 6/6] refactor(analytics): rename old analytics to analyticsV1 --- src/Analytics.php | 2 +- src/AnalyticsEvents.php | 32 ++-- src/AnalyticsEventsV1.php | 47 ++++++ src/AnalyticsEventsV2.php | 45 ------ src/AnalyticsRule.php | 27 +++- src/AnalyticsRuleV1.php | 30 ++++ src/AnalyticsRuleV2.php | 51 ------ src/AnalyticsRules.php | 61 ++++---- src/AnalyticsRulesV1.php | 75 +++++++++ src/AnalyticsRulesV2.php | 70 --------- src/{AnalyticsV2.php => AnalyticsV1.php} | 12 +- src/Client.php | 10 +- tests/Feature/AnalyticsEventsTest.php | 189 ++++++++++++++++------- tests/Feature/AnalyticsEventsV1Test.php | 83 ++++++++++ tests/Feature/AnalyticsEventsV2Test.php | 168 -------------------- tests/Feature/AnalyticsRulesTest.php | 163 +++++++++++++++---- tests/Feature/AnalyticsRulesV1Test.php | 78 ++++++++++ tests/Feature/AnalyticsRulesV2Test.php | 175 --------------------- 18 files changed, 659 insertions(+), 659 deletions(-) create mode 100644 src/AnalyticsEventsV1.php delete mode 100644 src/AnalyticsEventsV2.php create mode 100644 src/AnalyticsRuleV1.php delete mode 100644 src/AnalyticsRuleV2.php create mode 100644 src/AnalyticsRulesV1.php delete mode 100644 src/AnalyticsRulesV2.php rename src/{AnalyticsV2.php => AnalyticsV1.php} (66%) create mode 100644 tests/Feature/AnalyticsEventsV1Test.php delete mode 100644 tests/Feature/AnalyticsEventsV2Test.php create mode 100644 tests/Feature/AnalyticsRulesV1Test.php delete mode 100644 tests/Feature/AnalyticsRulesV2Test.php diff --git a/src/Analytics.php b/src/Analytics.php index 2f161dd1..4591de04 100644 --- a/src/Analytics.php +++ b/src/Analytics.php @@ -32,4 +32,4 @@ public function events() } return $this->events; } -} +} \ No newline at end of file diff --git a/src/AnalyticsEvents.php b/src/AnalyticsEvents.php index 6b7180bf..6b2ad484 100644 --- a/src/AnalyticsEvents.php +++ b/src/AnalyticsEvents.php @@ -4,6 +4,8 @@ /** * Class AnalyticsEvents + * + * Implements the updated analytics events API for Typesense + * * @package \Typesense */ @@ -11,37 +13,33 @@ class AnalyticsEvents { const RESOURCE_PATH = '/analytics/events'; - /** - * @var ApiCall - */ private ApiCall $apiCall; - /** - * AnalyticsEvents constructor. - * - * @param ApiCall $apiCall - */ public function __construct(ApiCall $apiCall) { $this->apiCall = $apiCall; } /** - * @param array $params - * - * @return array + * Create an analytics event + * + * @param array $params Event parameters including name, event_type, and data + * @return array Response from the API * @throws TypesenseClientError|HttpClientException */ - public function create($params) + public function create(array $params) { - return $this->apiCall->post($this->endpoint_path(), $params); + return $this->apiCall->post(self::RESOURCE_PATH, $params); } /** - * @return string + * Retrieve analytics events + * + * @param array $params Query parameters + * @return array Response from the API */ - private function endpoint_path($operation = null) + public function retrieve(array $params = []) { - return self::RESOURCE_PATH . ($operation === null ? '' : "/$operation"); + return $this->apiCall->get(self::RESOURCE_PATH, $params); } -} +} \ No newline at end of file diff --git a/src/AnalyticsEventsV1.php b/src/AnalyticsEventsV1.php new file mode 100644 index 00000000..407e1a6b --- /dev/null +++ b/src/AnalyticsEventsV1.php @@ -0,0 +1,47 @@ +apiCall = $apiCall; + } + + /** + * @param array $params + * + * @return array + * @throws TypesenseClientError|HttpClientException + */ + public function create($params) + { + return $this->apiCall->post($this->endpoint_path(), $params); + } + + /** + * @return string + */ + private function endpoint_path($operation = null) + { + return self::RESOURCE_PATH . ($operation === null ? '' : "/$operation"); + } +} diff --git a/src/AnalyticsEventsV2.php b/src/AnalyticsEventsV2.php deleted file mode 100644 index ae9f4c6b..00000000 --- a/src/AnalyticsEventsV2.php +++ /dev/null @@ -1,45 +0,0 @@ -apiCall = $apiCall; - } - - /** - * Create an analytics event - * - * @param array $params Event parameters including name, event_type, and data - * @return array Response from the API - * @throws TypesenseClientError|HttpClientException - */ - public function create(array $params) - { - return $this->apiCall->post(self::RESOURCE_PATH, $params); - } - - /** - * Retrieve analytics events - * - * @param array $params Query parameters - * @return array Response from the API - */ - public function retrieve(array $params = []) - { - return $this->apiCall->get(self::RESOURCE_PATH, $params); - } -} \ No newline at end of file diff --git a/src/AnalyticsRule.php b/src/AnalyticsRule.php index a574e301..2c303960 100644 --- a/src/AnalyticsRule.php +++ b/src/AnalyticsRule.php @@ -4,27 +4,48 @@ class AnalyticsRule { - private $ruleName; + private string $ruleName; private ApiCall $apiCall; public function __construct(string $ruleName, ApiCall $apiCall) { $this->ruleName = $ruleName; - $this->apiCall = $apiCall; + $this->apiCall = $apiCall; } + /** + * Retrieve a specific analytics rule + * + * @return array Response from the API + */ public function retrieve() { return $this->apiCall->get($this->endpointPath(), []); } + /** + * Delete a specific analytics rule + * + * @return array Response from the API + */ public function delete() { return $this->apiCall->delete($this->endpointPath()); } + /** + * Update a specific analytics rule + * + * @param array $params Rule parameters + * @return array Response from the API + */ + public function update(array $params) + { + return $this->apiCall->put($this->endpointPath(), $params); + } + private function endpointPath() { return AnalyticsRules::RESOURCE_PATH . '/' . encodeURIComponent($this->ruleName); } -} +} \ No newline at end of file diff --git a/src/AnalyticsRuleV1.php b/src/AnalyticsRuleV1.php new file mode 100644 index 00000000..f4b52b52 --- /dev/null +++ b/src/AnalyticsRuleV1.php @@ -0,0 +1,30 @@ +ruleName = $ruleName; + $this->apiCall = $apiCall; + } + + public function retrieve() + { + return $this->apiCall->get($this->endpointPath(), []); + } + + public function delete() + { + return $this->apiCall->delete($this->endpointPath()); + } + + private function endpointPath() + { + return AnalyticsRulesV1::RESOURCE_PATH . '/' . encodeURIComponent($this->ruleName); + } +} diff --git a/src/AnalyticsRuleV2.php b/src/AnalyticsRuleV2.php deleted file mode 100644 index 270bb688..00000000 --- a/src/AnalyticsRuleV2.php +++ /dev/null @@ -1,51 +0,0 @@ -ruleName = $ruleName; - $this->apiCall = $apiCall; - } - - /** - * Retrieve a specific analytics rule - * - * @return array Response from the API - */ - public function retrieve() - { - return $this->apiCall->get($this->endpointPath(), []); - } - - /** - * Delete a specific analytics rule - * - * @return array Response from the API - */ - public function delete() - { - return $this->apiCall->delete($this->endpointPath()); - } - - /** - * Update a specific analytics rule - * - * @param array $params Rule parameters - * @return array Response from the API - */ - public function update(array $params) - { - return $this->apiCall->put($this->endpointPath(), $params); - } - - private function endpointPath() - { - return AnalyticsRulesV2::RESOURCE_PATH . '/' . encodeURIComponent($this->ruleName); - } -} \ No newline at end of file diff --git a/src/AnalyticsRules.php b/src/AnalyticsRules.php index 444d1cb2..199b2c6b 100644 --- a/src/AnalyticsRules.php +++ b/src/AnalyticsRules.php @@ -7,69 +7,64 @@ class AnalyticsRules implements \ArrayAccess const RESOURCE_PATH = '/analytics/rules'; private ApiCall $apiCall; - private $analyticsRules = []; public function __construct(ApiCall $apiCall) { $this->apiCall = $apiCall; } - public function __get($ruleName) - { - if (!isset($this->analyticsRules[$ruleName])) { - $this->analyticsRules[$ruleName] = new AnalyticsRule($ruleName, $this->apiCall); - } - return $this->analyticsRules[$ruleName]; - } - - public function upsert($ruleName, $params) + /** + * Create multiple analytics rules + * + * @param array $rules Array of rule objects + * @return array Response from the API + */ + public function create(array $rules) { - return $this->apiCall->put($this->endpoint_path($ruleName), $params); + return $this->apiCall->post(self::RESOURCE_PATH, $rules); } + /** + * Retrieve all analytics rules + * + * @return array Response from the API + */ public function retrieve() { - return $this->apiCall->get($this->endpoint_path(), []); + return $this->apiCall->get(self::RESOURCE_PATH, []); } - private function endpoint_path($operation = null) + /** + * Get a specific rule by name + * + * @param string $ruleName + * @return AnalyticsRule + */ + public function __get($ruleName) { - return self::RESOURCE_PATH . ($operation === null ? '' : "/" . encodeURIComponent($operation)); + return new AnalyticsRule($ruleName, $this->apiCall); } /** - * @inheritDoc + * ArrayAccess implementation for backwards compatibility */ public function offsetExists($offset): bool { - return isset($this->analyticsRules[$offset]); + return true; // Rules can be accessed by name } - /** - * @inheritDoc - */ public function offsetGet($offset): AnalyticsRule { - if (!isset($this->analyticsRules[$offset])) { - $this->analyticsRules[$offset] = new AnalyticsRule($offset, $this->apiCall); - } - - return $this->analyticsRules[$offset]; + return new AnalyticsRule($offset, $this->apiCall); } - /** - * @inheritDoc - */ public function offsetSet($offset, $value): void { - $this->analyticsRules[$offset] = $value; + // Not implemented for read-only access } - /** - * @inheritDoc - */ public function offsetUnset($offset): void { - unset($this->analyticsRules[$offset]); + // Not implemented for read-only access } -} +} \ No newline at end of file diff --git a/src/AnalyticsRulesV1.php b/src/AnalyticsRulesV1.php new file mode 100644 index 00000000..59924fc7 --- /dev/null +++ b/src/AnalyticsRulesV1.php @@ -0,0 +1,75 @@ +apiCall = $apiCall; + } + + public function __get($ruleName) + { + if (!isset($this->analyticsRules[$ruleName])) { + $this->analyticsRules[$ruleName] = new AnalyticsRuleV1($ruleName, $this->apiCall); + } + return $this->analyticsRules[$ruleName]; + } + + public function upsert($ruleName, $params) + { + return $this->apiCall->put($this->endpoint_path($ruleName), $params); + } + + public function retrieve() + { + return $this->apiCall->get($this->endpoint_path(), []); + } + + private function endpoint_path($operation = null) + { + return self::RESOURCE_PATH . ($operation === null ? '' : "/" . encodeURIComponent($operation)); + } + + /** + * @inheritDoc + */ + public function offsetExists($offset): bool + { + return isset($this->analyticsRules[$offset]); + } + + /** + * @inheritDoc + */ + public function offsetGet($offset): AnalyticsRuleV1 + { + if (!isset($this->analyticsRules[$offset])) { + $this->analyticsRules[$offset] = new AnalyticsRuleV1($offset, $this->apiCall); + } + + return $this->analyticsRules[$offset]; + } + + /** + * @inheritDoc + */ + public function offsetSet($offset, $value): void + { + $this->analyticsRules[$offset] = $value; + } + + /** + * @inheritDoc + */ + public function offsetUnset($offset): void + { + unset($this->analyticsRules[$offset]); + } +} diff --git a/src/AnalyticsRulesV2.php b/src/AnalyticsRulesV2.php deleted file mode 100644 index 39639249..00000000 --- a/src/AnalyticsRulesV2.php +++ /dev/null @@ -1,70 +0,0 @@ -apiCall = $apiCall; - } - - /** - * Create multiple analytics rules - * - * @param array $rules Array of rule objects - * @return array Response from the API - */ - public function create(array $rules) - { - return $this->apiCall->post(self::RESOURCE_PATH, $rules); - } - - /** - * Retrieve all analytics rules - * - * @return array Response from the API - */ - public function retrieve() - { - return $this->apiCall->get(self::RESOURCE_PATH, []); - } - - /** - * Get a specific rule by name - * - * @param string $ruleName - * @return AnalyticsRuleV2 - */ - public function __get($ruleName) - { - return new AnalyticsRuleV2($ruleName, $this->apiCall); - } - - /** - * ArrayAccess implementation for backwards compatibility - */ - public function offsetExists($offset): bool - { - return true; // Rules can be accessed by name - } - - public function offsetGet($offset): AnalyticsRuleV2 - { - return new AnalyticsRuleV2($offset, $this->apiCall); - } - - public function offsetSet($offset, $value): void - { - // Not implemented for read-only access - } - - public function offsetUnset($offset): void - { - // Not implemented for read-only access - } -} \ No newline at end of file diff --git a/src/AnalyticsV2.php b/src/AnalyticsV1.php similarity index 66% rename from src/AnalyticsV2.php rename to src/AnalyticsV1.php index 1a3629dc..371a1bb0 100644 --- a/src/AnalyticsV2.php +++ b/src/AnalyticsV1.php @@ -2,15 +2,15 @@ namespace Typesense; -class AnalyticsV2 +class AnalyticsV1 { const RESOURCE_PATH = '/analytics'; private ApiCall $apiCall; - private AnalyticsRulesV2 $rules; + private AnalyticsRulesV1 $rules; - private AnalyticsEventsV2 $events; + private AnalyticsEventsV1 $events; public function __construct(ApiCall $apiCall) { @@ -20,7 +20,7 @@ public function __construct(ApiCall $apiCall) public function rules() { if (!isset($this->rules)) { - $this->rules = new AnalyticsRulesV2($this->apiCall); + $this->rules = new AnalyticsRulesV1($this->apiCall); } return $this->rules; } @@ -28,8 +28,8 @@ public function rules() public function events() { if (!isset($this->events)) { - $this->events = new AnalyticsEventsV2($this->apiCall); + $this->events = new AnalyticsEventsV1($this->apiCall); } return $this->events; } -} \ No newline at end of file +} diff --git a/src/Client.php b/src/Client.php index d0cb7b81..277c1f41 100644 --- a/src/Client.php +++ b/src/Client.php @@ -71,14 +71,14 @@ class Client public Presets $presets; /** - * @var Analytics + * @var AnalyticsV1 */ - public Analytics $analytics; + public AnalyticsV1 $analyticsV1; /** - * @var AnalyticsV2 + * @var Analytics */ - public AnalyticsV2 $analyticsV2; + public Analytics $analytics; /** * @var Stemming @@ -123,7 +123,7 @@ public function __construct(array $config) $this->multiSearch = new MultiSearch($this->apiCall); $this->presets = new Presets($this->apiCall); $this->analytics = new Analytics($this->apiCall); - $this->analyticsV2 = new AnalyticsV2($this->apiCall); + $this->analyticsV1 = new AnalyticsV1($this->apiCall); $this->stemming = new Stemming($this->apiCall); $this->conversations = new Conversations($this->apiCall); $this->nlSearchModels = new NLSearchModels($this->apiCall); diff --git a/tests/Feature/AnalyticsEventsTest.php b/tests/Feature/AnalyticsEventsTest.php index 6b9a433c..3f9ff3e8 100644 --- a/tests/Feature/AnalyticsEventsTest.php +++ b/tests/Feature/AnalyticsEventsTest.php @@ -7,77 +7,162 @@ class AnalyticsEventsTest extends TestCase { - private $ruleName = 'product_queries_aggregation'; + private $ruleName = 'test__rule'; + private $ruleConfiguration; protected function setUp(): void { parent::setUp(); - - if ($this->isV30OrAbove()) { - $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); - } - - $this->client()->collections->create([ - "name" => "products", - "fields" => [ - [ - "name" => "title", - "type" => "string" - ], - [ - "name" => "popularity", - "type" => "int32", - "optional" => true - ] - ] - ]); - $this->client()->analytics->rules()->upsert($this->ruleName, [ - "name" => "products_popularity", + + $this->ruleConfiguration = [ + "name" => $this->ruleName, "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", "params" => [ - "source" => [ - "collections" => [ - "products" - ], - "events" => [ - [ - "type" => "click", - "weight" => 1, - "name" => "products_click_event" - ] - ] - ], - "destination" => [ - "collection" => "products", - "counter_field" => "popularity" - ] + "counter_field" => "popularity", + "weight" => 1 ] - ]); + ]; + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('New Analytics API is not supported in Typesense 29.0 and below'); + } + + try { + $this->client()->collections->create([ + 'name' => 'test_products', + 'fields' => [ + ['name' => 'company_name', 'type' => 'string'], + ['name' => 'num_employees', 'type' => 'int32'], + ['name' => 'country', 'type' => 'string', 'facet' => true], + ['name' => 'popularity', 'type' => 'int32', 'optional' => true] + ], + 'default_sorting_field' => 'num_employees' + ]); + } catch (Exception $e) { + } + + try { + $this->client()->analytics->rules()->create([$this->ruleConfiguration]); + } catch (Exception $e) { + } } protected function tearDown(): void { - parent::tearDown(); - - if (!$this->isV30OrAbove()) { + if ($this->isV30OrAbove()) { try { - $this->client()->analytics->rules()->{'product_queries_aggregation'}->delete(); + $rules = $this->client()->analytics->rules()->retrieve(); + if (is_array($rules)) { + foreach ($rules as $rule) { + if (strpos($rule['name'], 'test__') === 0) { + try { + $this->client()->analytics->rules()[$rule['name']]->delete(); + } catch (Exception $e) { + } + } + } + } + } catch (Exception $e) { + } + + try { + $this->client()->collections['test_products']->delete(); } catch (Exception $e) { } } } - public function testCanCreateAnEvent(): void + public function testCanCreateEventsWithAPI(): void + { + $event = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_ids" => ["1", "2"], + "user_id" => "test_user" + ] + ]; + + $response = $this->client()->analytics->events()->create($event); + $this->assertIsArray($response); + } + + public function testCanCreateMultipleEventsWithAPI(): void + { + $event1 = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user_1" + ] + ]; + + $event2 = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "2", + "user_id" => "test_user_2" + ] + ]; + + $response1 = $this->client()->analytics->events()->create($event1); + $this->assertIsArray($response1); + + $response2 = $this->client()->analytics->events()->create($event2); + $this->assertIsArray($response2); + } + + public function testCanRetrieveEventsWithAPI(): void { - $response = $this->client()->analytics->events()->create([ - "type" => "click", - "name" => "products_click_event", + $event = [ + "name" => $this->ruleName, + "event_type" => "click", "data" => [ - "q" => "nike shoes", - "doc_id" => "1024", - "user_id" => "111112" + "doc_id" => "1", + "user_id" => "test_user" ] + ]; + + $this->client()->analytics->events()->create($event); + + $response = $this->client()->analytics->events()->retrieve([ + 'user_id' => 'test_user', + 'name' => $this->ruleName, + 'n'=> 10 ]); - $this->assertTrue($response['ok']); + + $this->assertIsArray($response); + } + + public function testCanCreateEventWithDifferentEventTypes(): void + { + $clickEvent = [ + "name" => $this->ruleName, + "event_type" => "click", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user" + ] + ]; + + $conversionEvent = [ + "name" => $this->ruleName, + "event_type" => "conversion", + "data" => [ + "doc_id" => "1", + "user_id" => "test_user" + ] + ]; + + $clickResponse = $this->client()->analytics->events()->create($clickEvent); + $this->assertIsArray($clickResponse); + + $conversionResponse = $this->client()->analytics->events()->create($conversionEvent); + $this->assertIsArray($conversionResponse); } -} +} \ No newline at end of file diff --git a/tests/Feature/AnalyticsEventsV1Test.php b/tests/Feature/AnalyticsEventsV1Test.php new file mode 100644 index 00000000..12c809c7 --- /dev/null +++ b/tests/Feature/AnalyticsEventsV1Test.php @@ -0,0 +1,83 @@ +isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + + $this->client()->collections->create([ + "name" => "products", + "fields" => [ + [ + "name" => "title", + "type" => "string" + ], + [ + "name" => "popularity", + "type" => "int32", + "optional" => true + ] + ] + ]); + $this->client()->analyticsV1->rules()->upsert($this->ruleName, [ + "name" => "products_popularity", + "type" => "counter", + "params" => [ + "source" => [ + "collections" => [ + "products" + ], + "events" => [ + [ + "type" => "click", + "weight" => 1, + "name" => "products_click_event" + ] + ] + ], + "destination" => [ + "collection" => "products", + "counter_field" => "popularity" + ] + ] + ]); + } + + protected function tearDown(): void + { + parent::tearDown(); + + if (!$this->isV30OrAbove()) { + try { + $this->client()->analyticsV1->rules()->{'product_queries_aggregation'}->delete(); + } catch (Exception $e) { + } + } + } + + public function testCanCreateAnEvent(): void + { + $response = $this->client()->analyticsV1->events()->create([ + "type" => "click", + "name" => "products_click_event", + "data" => [ + "q" => "nike shoes", + "doc_id" => "1024", + "user_id" => "111112" + ] + ]); + $this->assertTrue($response['ok']); + } +} diff --git a/tests/Feature/AnalyticsEventsV2Test.php b/tests/Feature/AnalyticsEventsV2Test.php deleted file mode 100644 index f6cdb990..00000000 --- a/tests/Feature/AnalyticsEventsV2Test.php +++ /dev/null @@ -1,168 +0,0 @@ -ruleConfiguration = [ - "name" => $this->ruleName, - "type" => "counter", - "collection" => "test_products", - "event_type" => "click", - "rule_tag" => "test_tag", - "params" => [ - "counter_field" => "popularity", - "weight" => 1 - ] - ]; - - if (!$this->isV30OrAbove()) { - $this->markTestSkipped('New Analytics API is not supported in Typesense v29.0 and below'); - } - - try { - $this->client()->collections->create([ - 'name' => 'test_products', - 'fields' => [ - ['name' => 'company_name', 'type' => 'string'], - ['name' => 'num_employees', 'type' => 'int32'], - ['name' => 'country', 'type' => 'string', 'facet' => true], - ['name' => 'popularity', 'type' => 'int32', 'optional' => true] - ], - 'default_sorting_field' => 'num_employees' - ]); - } catch (Exception $e) { - } - - try { - $this->client()->analyticsV2->rules()->create([$this->ruleConfiguration]); - } catch (Exception $e) { - } - } - - protected function tearDown(): void - { - if (!$this->isV30OrAbove()) { - try { - $rules = $this->client()->analyticsV2->rules()->retrieve(); - if (is_array($rules)) { - foreach ($rules as $rule) { - if (strpos($rule['name'], 'test_v2_') === 0) { - try { - $this->client()->analyticsV2->rules()[$rule['name']]->delete(); - } catch (Exception $e) { - } - } - } - } - } catch (Exception $e) { - } - - try { - $this->client()->collections['test_products']->delete(); - } catch (Exception $e) { - } - } - } - - public function testCanCreateEventsWithV2API(): void - { - $event = [ - "name" => $this->ruleName, - "event_type" => "click", - "data" => [ - "doc_ids" => ["1", "2"], - "user_id" => "test_user" - ] - ]; - - $response = $this->client()->analyticsV2->events()->create($event); - $this->assertIsArray($response); - } - - public function testCanCreateMultipleEventsWithV2API(): void - { - $event1 = [ - "name" => $this->ruleName, - "event_type" => "click", - "data" => [ - "doc_id" => "1", - "user_id" => "test_user_1" - ] - ]; - - $event2 = [ - "name" => $this->ruleName, - "event_type" => "click", - "data" => [ - "doc_id" => "2", - "user_id" => "test_user_2" - ] - ]; - - $response1 = $this->client()->analyticsV2->events()->create($event1); - $this->assertIsArray($response1); - - $response2 = $this->client()->analyticsV2->events()->create($event2); - $this->assertIsArray($response2); - } - - public function testCanRetrieveEventsWithV2API(): void - { - $event = [ - "name" => $this->ruleName, - "event_type" => "click", - "data" => [ - "doc_id" => "1", - "user_id" => "test_user" - ] - ]; - - $this->client()->analyticsV2->events()->create($event); - - $response = $this->client()->analyticsV2->events()->retrieve([ - 'user_id' => 'test_user', - 'name' => $this->ruleName, - 'n'=> 10 - ]); - - $this->assertIsArray($response); - } - - public function testCanCreateEventWithDifferentEventTypes(): void - { - $clickEvent = [ - "name" => $this->ruleName, - "event_type" => "click", - "data" => [ - "doc_id" => "1", - "user_id" => "test_user" - ] - ]; - - $conversionEvent = [ - "name" => $this->ruleName, - "event_type" => "conversion", - "data" => [ - "doc_id" => "1", - "user_id" => "test_user" - ] - ]; - - $clickResponse = $this->client()->analyticsV2->events()->create($clickEvent); - $this->assertIsArray($clickResponse); - - $conversionResponse = $this->client()->analyticsV2->events()->create($conversionEvent); - $this->assertIsArray($conversionResponse); - } -} \ No newline at end of file diff --git a/tests/Feature/AnalyticsRulesTest.php b/tests/Feature/AnalyticsRulesTest.php index e5d84ead..9709bf60 100644 --- a/tests/Feature/AnalyticsRulesTest.php +++ b/tests/Feature/AnalyticsRulesTest.php @@ -3,76 +3,173 @@ namespace Feature; use Tests\TestCase; -use Typesense\Exceptions\ObjectNotFound; +use Typesense\Exceptions\RequestMalformed; use Exception; class AnalyticsRulesTest extends TestCase { - private $ruleName = 'test_rule'; - private $ruleConfiguration = [ - "type" => "popular_queries", - "params" => [ - "source" => [ - "collections" => ["products"] - ], - "destination" => [ - "collection" => "product_queries" - ], - "expand_query" => false, - "limit" => 1000 - ] - ]; - private $ruleUpsertResponse = null; + private $ruleName = 'test__rule'; + private $ruleConfiguration; protected function setUp(): void { parent::setUp(); - if ($this->isV30OrAbove()) { - $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + $this->ruleConfiguration = [ + "name" => $this->ruleName, + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 1 + ] + ]; + + if (!$this->isV30OrAbove()) { + $this->markTestSkipped('New Analytics API is not supported in Typesense 9.0 and below'); + } + + try { + $this->client()->collections->create([ + 'name' => 'test_products', + 'fields' => [ + ['name' => 'company_name', 'type' => 'string'], + ['name' => 'num_employees', 'type' => 'int32'], + ['name' => 'country', 'type' => 'string', 'facet' => true], + ['name' => 'popularity', 'type' => 'int32', 'optional' => true] + ], + 'default_sorting_field' => 'num_employees' + ]); + } catch (Exception $e) { } - $this->ruleUpsertResponse = $this->client()->analytics->rules()->upsert($this->ruleName, $this->ruleConfiguration); + try { + $this->client()->analytics->rules()->create([$this->ruleConfiguration]); + } catch (Exception $e) { + } } protected function tearDown(): void { - if (!$this->isV30OrAbove()) { + if ($this->isV30OrAbove()) { try { $rules = $this->client()->analytics->rules()->retrieve(); - if (is_array($rules) && isset($rules['rules'])) { - foreach ($rules['rules'] as $rule) { - $this->client()->analytics->rules()->{$rule['name']}->delete(); + if (is_array($rules)) { + foreach ($rules as $rule) { + if (strpos($rule['name'], 'test__') === 0) { + try { + $this->client()->analytics->rules()[$rule['name']]->delete(); + } catch (Exception $e) { + } + } } } } catch (Exception $e) { } + + try { + $this->client()->collections['test_products']->delete(); + } catch (Exception $e) { + } } } - public function testCanUpsertARule(): void + public function testCanCreateRulesWithAPI(): void { - $this->assertEquals($this->ruleName, $this->ruleUpsertResponse['name']); + $rules = [ + [ + "name" => "test_rule_1", + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 1 + ] + ], + [ + "name" => "test_rule_2", + "type" => "counter", + "collection" => "test_products", + "event_type" => "conversion", + "rule_tag" => "test_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 2 + ] + ] + ]; + + $response = $this->client()->analytics->rules()->create($rules); + $this->assertIsArray($response); + + $allRules = $this->client()->analytics->rules()->retrieve(); + $this->assertIsArray($allRules); + + $ruleNames = array_column($allRules, 'name'); + $this->assertContains('test_rule_1', $ruleNames); + $this->assertContains('test_rule_2', $ruleNames); } - public function testCanRetrieveARule(): void + public function testCanRetrieveARuleWithAPI(): void { $returnData = $this->client()->analytics->rules()[$this->ruleName]->retrieve(); - $this->assertEquals($returnData['name'], $this->ruleName); + $this->assertEquals($this->ruleName, $returnData['name']); + $this->assertEquals('counter', $returnData['type']); + $this->assertEquals('test_products', $returnData['collection']); } - public function testCanDeleteARule(): void + public function testCanUpdateARuleWithAPI(): void + { + $updateParams = [ + "type" => "counter", + "collection" => "test_products", + "event_type" => "click", + "rule_tag" => "updated_tag", + "params" => [ + "counter_field" => "popularity", + "weight" => 5 + ] + ]; + + $response = $this->client()->analytics->rules()[$this->ruleName]->update($updateParams); + $this->assertEquals($this->ruleName, $response['name']); + $this->assertEquals('updated_tag', $response['rule_tag']); + $this->assertEquals(5, $response['params']['weight']); + } + + public function testCanDeleteARuleWithAPI(): void { $returnData = $this->client()->analytics->rules()[$this->ruleName]->delete(); - $this->assertEquals($returnData['name'], $this->ruleName); + $this->assertEquals($this->ruleName, $returnData['name']); - $this->expectException(ObjectNotFound::class); + $this->expectException(RequestMalformed::class); $this->client()->analytics->rules()[$this->ruleName]->retrieve(); } - public function testCanRetrieveAllRules(): void + public function testCanRetrieveAllRulesWithAPI(): void { $returnData = $this->client()->analytics->rules()->retrieve(); - $this->assertCount(1, $returnData['rules']); + $this->assertIsArray($returnData); + $this->assertGreaterThanOrEqual(1, count($returnData)); + + $ruleNames = array_column($returnData, 'name'); + $this->assertContains('test__rule', $ruleNames); + $this->assertContains('test_rule_1', $ruleNames); + $this->assertContains('test_rule_2', $ruleNames); + } + + public function testArrayAccessCompatibility(): void + { + $rule = $this->client()->analytics->rules()[$this->ruleName]; + $this->assertInstanceOf('Typesense\AnalyticsRule', $rule); + + $this->assertTrue(isset($this->client()->analytics->rules()[$this->ruleName])); + + $rule = $this->client()->analytics->rules()[$this->ruleName]; + $this->assertInstanceOf('Typesense\AnalyticsRule', $rule); } -} +} \ No newline at end of file diff --git a/tests/Feature/AnalyticsRulesV1Test.php b/tests/Feature/AnalyticsRulesV1Test.php new file mode 100644 index 00000000..2f55468b --- /dev/null +++ b/tests/Feature/AnalyticsRulesV1Test.php @@ -0,0 +1,78 @@ + "popular_queries", + "params" => [ + "source" => [ + "collections" => ["products"] + ], + "destination" => [ + "collection" => "product_queries" + ], + "expand_query" => false, + "limit" => 1000 + ] + ]; + private $ruleUpsertResponse = null; + + protected function setUp(): void + { + parent::setUp(); + + if ($this->isV30OrAbove()) { + $this->markTestSkipped('Analytics is deprecated in Typesense v30+'); + } + + $this->ruleUpsertResponse = $this->client()->analyticsV1->rules()->upsert($this->ruleName, $this->ruleConfiguration); + } + + protected function tearDown(): void + { + if (!$this->isV30OrAbove()) { + try { + $rules = $this->client()->analyticsV1->rules()->retrieve(); + if (is_array($rules) && isset($rules['rules'])) { + foreach ($rules['rules'] as $rule) { + $this->client()->analyticsV1->rules()->{$rule['name']}->delete(); + } + } + } catch (Exception $e) { + } + } + } + + public function testCanUpsertARule(): void + { + $this->assertEquals($this->ruleName, $this->ruleUpsertResponse['name']); + } + + public function testCanRetrieveARule(): void + { + $returnData = $this->client()->analyticsV1->rules()[$this->ruleName]->retrieve(); + $this->assertEquals($returnData['name'], $this->ruleName); + } + + public function testCanDeleteARule(): void + { + $returnData = $this->client()->analyticsV1->rules()[$this->ruleName]->delete(); + $this->assertEquals($returnData['name'], $this->ruleName); + + $this->expectException(ObjectNotFound::class); + $this->client()->analyticsV1->rules()[$this->ruleName]->retrieve(); + } + + public function testCanRetrieveAllRules(): void + { + $returnData = $this->client()->analyticsV1->rules()->retrieve(); + $this->assertCount(1, $returnData['rules']); + } +} diff --git a/tests/Feature/AnalyticsRulesV2Test.php b/tests/Feature/AnalyticsRulesV2Test.php deleted file mode 100644 index ec3b5b32..00000000 --- a/tests/Feature/AnalyticsRulesV2Test.php +++ /dev/null @@ -1,175 +0,0 @@ -ruleConfiguration = [ - "name" => $this->ruleName, - "type" => "counter", - "collection" => "test_products", - "event_type" => "click", - "rule_tag" => "test_tag", - "params" => [ - "counter_field" => "popularity", - "weight" => 1 - ] - ]; - - if (!$this->isV30OrAbove()) { - $this->markTestSkipped('New Analytics API is not supported in Typesense v29.0 and below'); - } - - try { - $this->client()->collections->create([ - 'name' => 'test_products', - 'fields' => [ - ['name' => 'company_name', 'type' => 'string'], - ['name' => 'num_employees', 'type' => 'int32'], - ['name' => 'country', 'type' => 'string', 'facet' => true], - ['name' => 'popularity', 'type' => 'int32', 'optional' => true] - ], - 'default_sorting_field' => 'num_employees' - ]); - } catch (Exception $e) { - } - - try { - $this->client()->analyticsV2->rules()->create([$this->ruleConfiguration]); - } catch (Exception $e) { - } - } - - protected function tearDown(): void - { - if (!$this->isV30OrAbove()) { - try { - $rules = $this->client()->analyticsV2->rules()->retrieve(); - if (is_array($rules)) { - foreach ($rules as $rule) { - if (strpos($rule['name'], 'test_v2_') === 0) { - try { - $this->client()->analyticsV2->rules()[$rule['name']]->delete(); - } catch (Exception $e) { - } - } - } - } - } catch (Exception $e) { - } - - try { - $this->client()->collections['test_products']->delete(); - } catch (Exception $e) { - } - } - } - - public function testCanCreateRulesWithV2API(): void - { - $rules = [ - [ - "name" => "test_rule_1", - "type" => "counter", - "collection" => "test_products", - "event_type" => "click", - "rule_tag" => "test_tag", - "params" => [ - "counter_field" => "popularity", - "weight" => 1 - ] - ], - [ - "name" => "test_rule_2", - "type" => "counter", - "collection" => "test_products", - "event_type" => "conversion", - "rule_tag" => "test_tag", - "params" => [ - "counter_field" => "popularity", - "weight" => 2 - ] - ] - ]; - - $response = $this->client()->analyticsV2->rules()->create($rules); - $this->assertIsArray($response); - - $allRules = $this->client()->analyticsV2->rules()->retrieve(); - $this->assertIsArray($allRules); - - $ruleNames = array_column($allRules, 'name'); - $this->assertContains('test_rule_1', $ruleNames); - $this->assertContains('test_rule_2', $ruleNames); - } - - public function testCanRetrieveARuleWithV2API(): void - { - $returnData = $this->client()->analyticsV2->rules()[$this->ruleName]->retrieve(); - $this->assertEquals($this->ruleName, $returnData['name']); - $this->assertEquals('counter', $returnData['type']); - $this->assertEquals('test_products', $returnData['collection']); - } - - public function testCanUpdateARuleWithV2API(): void - { - $updateParams = [ - "type" => "counter", - "collection" => "test_products", - "event_type" => "click", - "rule_tag" => "updated_tag", - "params" => [ - "counter_field" => "popularity", - "weight" => 5 - ] - ]; - - $response = $this->client()->analyticsV2->rules()[$this->ruleName]->update($updateParams); - $this->assertEquals($this->ruleName, $response['name']); - $this->assertEquals('updated_tag', $response['rule_tag']); - $this->assertEquals(5, $response['params']['weight']); - } - - public function testCanDeleteARuleWithV2API(): void - { - $returnData = $this->client()->analyticsV2->rules()[$this->ruleName]->delete(); - $this->assertEquals($this->ruleName, $returnData['name']); - - $this->expectException(RequestMalformed::class); - $this->client()->analyticsV2->rules()[$this->ruleName]->retrieve(); - } - - public function testCanRetrieveAllRulesWithV2API(): void - { - $returnData = $this->client()->analyticsV2->rules()->retrieve(); - $this->assertIsArray($returnData); - $this->assertGreaterThanOrEqual(1, count($returnData)); - - $ruleNames = array_column($returnData, 'name'); - $this->assertContains('test_v2_rule', $ruleNames); - $this->assertContains('test_rule_1', $ruleNames); - $this->assertContains('test_rule_2', $ruleNames); - } - - public function testArrayAccessCompatibility(): void - { - $rule = $this->client()->analyticsV2->rules()[$this->ruleName]; - $this->assertInstanceOf('Typesense\AnalyticsRuleV2', $rule); - - $this->assertTrue(isset($this->client()->analyticsV2->rules()[$this->ruleName])); - - $rule = $this->client()->analyticsV2->rules()[$this->ruleName]; - $this->assertInstanceOf('Typesense\AnalyticsRuleV2', $rule); - } -} \ No newline at end of file