diff --git a/.gitignore b/.gitignore index 8228c840..7e50fb64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea .tmp +.phpunit.result.cache /composer.lock +phpunit.xml vendor diff --git a/README.md b/README.md index 03cd0ce0..e4da1ee1 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,14 @@ Run Typesense Server: composer run-script typesenseServer ``` +Run tests: + +```shell script +docker compose up +cp phpunit.xml.dist phpunit.xml +composer run-script test +``` + ## Credits This client was originally developed by [Abdullah Al-Faqeir](https://github.org/abdullahfaqeir) from diff --git a/composer.json b/composer.json index 757297e7..1f140e87 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,11 @@ "Typesense\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, "require": { "php": ">=7.4", "ext-json": "*", @@ -42,6 +47,7 @@ "psr/http-factory": "^1.0" }, "require-dev": { + "phpunit/phpunit": "^11.2", "squizlabs/php_codesniffer": "3.*", "symfony/http-client": "^5.2" }, @@ -57,6 +63,7 @@ "Composer\\Config::disableProcessTimeout", "docker-compose up" ], + "test": "vendor/bin/phpunit", "lint": "phpcs -v", "lint:fix": "phpcbf" } diff --git a/examples/collection_operations.php b/examples/collection_operations.php index 1068c312..21bf741c 100644 --- a/examples/collection_operations.php +++ b/examples/collection_operations.php @@ -194,7 +194,7 @@ echo "--------Delete Document-------\n"; echo "\n"; echo "--------Import Documents-------\n"; - $docsToImport = []; + $docsToImport = []; $exportedDocStrsArray = explode('\n', $exportedDocStrs); foreach ($exportedDocStrsArray as $exportedDocStr) { $docsToImport[] = json_decode($exportedDocStr, true); diff --git a/phpcs.xml b/phpcs.xml index e7ff5dab..3b3ac751 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -5,6 +5,7 @@ src examples + tests/ */src/Standards/*/Tests/*\.(inc|css|js)$ */tests/Core/*/*\.(inc|css|js)$ @@ -42,4 +43,4 @@ - \ No newline at end of file + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..d79f0a2e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + tests/Feature + + + + + + + + + + diff --git a/src/Alias.php b/src/Alias.php index 5dd76189..6f1e21d4 100644 --- a/src/Alias.php +++ b/src/Alias.php @@ -14,7 +14,6 @@ */ class Alias { - /** * @var string */ diff --git a/src/Aliases.php b/src/Aliases.php index d1c76cf1..70b9798a 100644 --- a/src/Aliases.php +++ b/src/Aliases.php @@ -14,7 +14,6 @@ */ class Aliases implements \ArrayAccess { - public const RESOURCE_PATH = '/aliases'; /** diff --git a/src/AnalyticsRule.php b/src/AnalyticsRule.php index bc8755f6..9ea6f3f5 100644 --- a/src/AnalyticsRule.php +++ b/src/AnalyticsRule.php @@ -10,7 +10,7 @@ class AnalyticsRule public function __construct(string $ruleName, ApiCall $apiCall) { $this->ruleName = $ruleName; - $this->apiCall = $apiCall; + $this->apiCall = $apiCall; } public function retrieve() diff --git a/src/ApiCall.php b/src/ApiCall.php index 563642c6..a114b592 100644 --- a/src/ApiCall.php +++ b/src/ApiCall.php @@ -29,7 +29,6 @@ */ class ApiCall { - private const API_KEY_HEADER_NAME = 'X-TYPESENSE-API-KEY'; /** diff --git a/src/Client.php b/src/Client.php index bde52c53..00d73858 100644 --- a/src/Client.php +++ b/src/Client.php @@ -92,7 +92,7 @@ public function __construct(array $config) $this->apiCall = new ApiCall($this->config); $this->collections = new Collections($this->apiCall); - $this->stopwords = new Stopwords($this->apiCall); + $this->stopwords = new Stopwords($this->apiCall); $this->aliases = new Aliases($this->apiCall); $this->keys = new Keys($this->apiCall); $this->debug = new Debug($this->apiCall); diff --git a/src/Collections.php b/src/Collections.php index 9385aa38..2a224ace 100644 --- a/src/Collections.php +++ b/src/Collections.php @@ -14,7 +14,6 @@ */ class Collections implements \ArrayAccess { - public const RESOURCE_PATH = '/collections'; /** diff --git a/src/Document.php b/src/Document.php index 617bf9ee..ba970f68 100644 --- a/src/Document.php +++ b/src/Document.php @@ -14,7 +14,6 @@ */ class Document { - /** * @var string */ diff --git a/src/Documents.php b/src/Documents.php index 641536d6..4c0c166c 100644 --- a/src/Documents.php +++ b/src/Documents.php @@ -14,7 +14,6 @@ */ class Documents implements \ArrayAccess { - public const RESOURCE_PATH = 'documents'; /** diff --git a/src/Exceptions/ConfigError.php b/src/Exceptions/ConfigError.php index b4fcc7cb..fa19f7b7 100644 --- a/src/Exceptions/ConfigError.php +++ b/src/Exceptions/ConfigError.php @@ -11,5 +11,4 @@ */ class ConfigError extends TypesenseClientError { - } diff --git a/src/Exceptions/HTTPStatus0Error.php b/src/Exceptions/HTTPStatus0Error.php index e2f2752c..ddd3b0ba 100644 --- a/src/Exceptions/HTTPStatus0Error.php +++ b/src/Exceptions/HTTPStatus0Error.php @@ -10,5 +10,4 @@ */ class HTTPStatus0Error extends TypesenseClientError { - } diff --git a/src/Exceptions/ObjectAlreadyExists.php b/src/Exceptions/ObjectAlreadyExists.php index 956cffd1..01bc7d2e 100644 --- a/src/Exceptions/ObjectAlreadyExists.php +++ b/src/Exceptions/ObjectAlreadyExists.php @@ -11,5 +11,4 @@ */ class ObjectAlreadyExists extends TypesenseClientError { - } diff --git a/src/Exceptions/ObjectNotFound.php b/src/Exceptions/ObjectNotFound.php index 7cae4b82..0b1d95bd 100644 --- a/src/Exceptions/ObjectNotFound.php +++ b/src/Exceptions/ObjectNotFound.php @@ -11,5 +11,4 @@ */ class ObjectNotFound extends TypesenseClientError { - } diff --git a/src/Exceptions/ObjectUnprocessable.php b/src/Exceptions/ObjectUnprocessable.php index 89a2f922..0a4b7972 100644 --- a/src/Exceptions/ObjectUnprocessable.php +++ b/src/Exceptions/ObjectUnprocessable.php @@ -11,5 +11,4 @@ */ class ObjectUnprocessable extends TypesenseClientError { - } diff --git a/src/Exceptions/RequestMalformed.php b/src/Exceptions/RequestMalformed.php index 2a7c42b7..5be3fe29 100644 --- a/src/Exceptions/RequestMalformed.php +++ b/src/Exceptions/RequestMalformed.php @@ -11,5 +11,4 @@ */ class RequestMalformed extends TypesenseClientError { - } diff --git a/src/Exceptions/RequestUnauthorized.php b/src/Exceptions/RequestUnauthorized.php index 47ec3533..bfa07a02 100644 --- a/src/Exceptions/RequestUnauthorized.php +++ b/src/Exceptions/RequestUnauthorized.php @@ -11,5 +11,4 @@ */ class RequestUnauthorized extends TypesenseClientError { - } diff --git a/src/Exceptions/ServerError.php b/src/Exceptions/ServerError.php index e13d18d0..5c1b3714 100644 --- a/src/Exceptions/ServerError.php +++ b/src/Exceptions/ServerError.php @@ -11,5 +11,4 @@ */ class ServerError extends TypesenseClientError { - } diff --git a/src/Exceptions/ServiceUnavailable.php b/src/Exceptions/ServiceUnavailable.php index 1b32bcb1..250e1e3f 100644 --- a/src/Exceptions/ServiceUnavailable.php +++ b/src/Exceptions/ServiceUnavailable.php @@ -11,5 +11,4 @@ */ class ServiceUnavailable extends TypesenseClientError { - } diff --git a/src/Exceptions/Timeout.php b/src/Exceptions/Timeout.php index 359eb338..7a2fc7bf 100644 --- a/src/Exceptions/Timeout.php +++ b/src/Exceptions/Timeout.php @@ -11,5 +11,4 @@ */ class Timeout extends TypesenseClientError { - } diff --git a/src/Exceptions/TypesenseClientError.php b/src/Exceptions/TypesenseClientError.php index 2f20c2a8..3011e1e1 100644 --- a/src/Exceptions/TypesenseClientError.php +++ b/src/Exceptions/TypesenseClientError.php @@ -13,7 +13,6 @@ */ class TypesenseClientError extends Exception { - public function setMessage(string $message): TypesenseClientError { $this->message = $message; diff --git a/src/Key.php b/src/Key.php index af7a9b1f..2539de16 100644 --- a/src/Key.php +++ b/src/Key.php @@ -14,7 +14,6 @@ */ class Key { - /** * @var ApiCall */ diff --git a/src/Keys.php b/src/Keys.php index 740d9098..fc7b0008 100644 --- a/src/Keys.php +++ b/src/Keys.php @@ -14,7 +14,6 @@ */ class Keys implements \ArrayAccess { - public const RESOURCE_PATH = '/keys'; /** @@ -59,15 +58,16 @@ public function generateScopedSearchKey( string $searchKey, array $parameters ): string { - $paramStr = json_encode($parameters, JSON_THROW_ON_ERROR); - $digest = base64_encode( + $paramStr = json_encode($parameters, JSON_THROW_ON_ERROR); + $digest = base64_encode( hash_hmac( 'sha256', mb_convert_encoding($paramStr, 'UTF-8', 'ISO-8859-1'), mb_convert_encoding($searchKey, 'UTF-8', 'ISO-8859-1'), - true) + true + ) ); - $keyPrefix = substr($searchKey, 0, 4); + $keyPrefix = substr($searchKey, 0, 4); $rawScopedKey = sprintf( '%s%s%s', mb_convert_encoding($digest, 'ISO-8859-1', 'UTF-8'), diff --git a/src/Lib/Configuration.php b/src/Lib/Configuration.php index 54f0e9ea..da53245c 100644 --- a/src/Lib/Configuration.php +++ b/src/Lib/Configuration.php @@ -20,7 +20,6 @@ */ class Configuration { - /** * @var Node[] */ @@ -219,8 +218,8 @@ public function getClient(): ClientInterface { return new HttpMethodsClient( $this->client ?? Psr18ClientDiscovery::find(), - Psr17FactoryDiscovery::findRequestFactory(), - Psr17FactoryDiscovery::findStreamFactory(), + Psr17FactoryDiscovery::findRequestFactory(), + Psr17FactoryDiscovery::findStreamFactory(), ); } } diff --git a/src/Lib/Node.php b/src/Lib/Node.php index 3500baae..1286ba5d 100644 --- a/src/Lib/Node.php +++ b/src/Lib/Node.php @@ -11,7 +11,6 @@ */ class Node { - /** * @var string */ diff --git a/src/Override.php b/src/Override.php index d50855fe..6061f5bb 100644 --- a/src/Override.php +++ b/src/Override.php @@ -14,7 +14,6 @@ */ class Override { - /** * @var string */ diff --git a/src/Overrides.php b/src/Overrides.php index a7cc48fa..46de7b6d 100644 --- a/src/Overrides.php +++ b/src/Overrides.php @@ -14,7 +14,6 @@ */ class Overrides implements \ArrayAccess { - public const RESOURCE_PATH = 'overrides'; /** diff --git a/src/Presets.php b/src/Presets.php index b0f56c01..29edc84c 100644 --- a/src/Presets.php +++ b/src/Presets.php @@ -63,7 +63,7 @@ public function get() */ public function put(array $options = []) { - $presetName = $options['preset_name']; + $presetName = $options['preset_name']; $presetsData = $options['preset_data']; return $this->apiCall->put($this->endpointPath($presetName), $presetsData); diff --git a/src/Synonym.php b/src/Synonym.php index 5ce46c15..0857423a 100644 --- a/src/Synonym.php +++ b/src/Synonym.php @@ -12,7 +12,6 @@ */ class Synonym { - /** * @var string */ diff --git a/src/Synonyms.php b/src/Synonyms.php index f48f2144..57c8d1a9 100644 --- a/src/Synonyms.php +++ b/src/Synonyms.php @@ -12,7 +12,6 @@ */ class Synonyms implements \ArrayAccess { - public const RESOURCE_PATH = 'synonyms'; /** diff --git a/tests/Feature/CollectionTest.php b/tests/Feature/CollectionTest.php new file mode 100644 index 00000000..2850d8e0 --- /dev/null +++ b/tests/Feature/CollectionTest.php @@ -0,0 +1,40 @@ +getSchema('books'); + + $response = $this->client()->collections->create($schema); + + $this->assertEquals('books', $response['name']); + } + + public function testCanRetrieveCollection(): void + { + $schema = $this->getSchema('books'); + $this->client()->collections->create($schema); + + $response = $this->client()->collections['books']->retrieve(); + + $this->assertEquals('books', $response['name']); + } + + public function testCanDeleteCollection(): void + { + $schema = $this->getSchema('books'); + $this->client()->collections->create($schema); + + $this->client()->collections['books']->delete(); + + $this->expectException(ObjectNotFound::class); + + $this->client()->collections['books']->retrieve(); + } +} diff --git a/tests/Feature/DocumentsTest.php b/tests/Feature/DocumentsTest.php new file mode 100644 index 00000000..eb78ba7f --- /dev/null +++ b/tests/Feature/DocumentsTest.php @@ -0,0 +1,27 @@ +setUpCollection('books'); + $this->setUpDocuments('books'); + } + + public function testCanUpdateDocumentsByFilter(): void + { + $document = ['publisher' => 'Renamed Publisher']; + + $response = $this->client()->collections['books']->documents->update($document, [ + 'filter_by' => 'publisher:=AwesomeBooks' + ]); + + $this->assertGreaterThan(0, $response['num_updated']); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 00000000..61512692 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,88 @@ +setUpTypesenseClient(); + } + + protected function tearDown(): void + { + $this->tearDownTypesense(); + } + + protected function client(): Client + { + return $this->typesenseClient; + } + + protected function getSchema(string $name): array + { + $path = __DIR__ . "/data/{$name}.schema.json"; + + return $this->loadFromDataDir($path); + } + + protected function getData(string $name): array + { + $path = __DIR__ . "/data/{$name}.data.json"; + + return $this->loadFromDataDir($path); + } + + private function loadFromDataDir(string $path): array + { + if (! file_exists($path)) { + return []; + } + + return json_decode( + file_get_contents($path), + true, + 512, + JSON_THROW_ON_ERROR + ); + } + + private function setUpTypesenseClient(): void + { + $this->typesenseClient = new Client([ + 'api_key' => $_ENV['TYPESENSE_API_KEY'], + 'nodes' => [ + [ + 'host' => $_ENV['TYPESENSE_NODE_HOST'], + 'port' => $_ENV['TYPESENSE_NODE_PORT'], + 'protocol' => $_ENV['TYPESENSE_NODE_PROTOCOL'] + ], + ] + ]); + } + + protected function setUpCollection(string $schema): void + { + $schema = $this->getSchema($schema); + $this->typesenseClient->collections->create($schema); + } + + protected function setUpDocuments(string $schema): void + { + $documents = $this->getData($schema); + $this->typesenseClient->collections[$schema]->documents->import($documents); + } + + private function tearDownTypesense(): void + { + $collections = $this->typesenseClient->collections->retrieve(); + foreach ($collections as $collection) { + $this->typesenseClient->collections[$collection['name']]->delete(); + } + } +} diff --git a/tests/data/books.data.json b/tests/data/books.data.json new file mode 100644 index 00000000..1ec5615d --- /dev/null +++ b/tests/data/books.data.json @@ -0,0 +1,34 @@ +[ + { + "title": "Book 1", + "description": "Wonderful Description", + "authors": [ + "Jane", + "John" + ], + "isbn": "1234567890", + "publisher": "AwesomeBooks", + "pages": 123 + }, + { + "title": "Book 2", + "description": "Another helpful description", + "authors": [ + "Jeff", + "Mia" + ], + "isbn": "1234567891", + "publisher": "AwesomeBooks", + "pages": 150 + }, + { + "title": "Book 3", + "description": "Another useless description", + "authors": [ + "Martin" + ], + "isbn": "1234567892", + "publisher": "PubLishEr", + "pages": 268 + } +] diff --git a/tests/data/books.schema.json b/tests/data/books.schema.json new file mode 100644 index 00000000..84f636ec --- /dev/null +++ b/tests/data/books.schema.json @@ -0,0 +1,30 @@ +{ + "name": "books", + "fields": [ + { + "name": "title", + "type": "string" + }, + { + "name": "description", + "type": "string" + }, + { + "name": "authors", + "type": "string[]", + "facet": true + }, + { + "name": "isbn", + "type": "string" + }, + { + "name": "publisher", + "type": "string" + }, + { + "name": "pages", + "type": "int32" + } + ] +}