From c310434eebcaab9578c0569593cf950aa9ef39cc Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 16:39:35 +1300 Subject: [PATCH 1/7] Update dependencies --- composer.json | 6 +++--- composer.lock | 45 ++++++++++++++++++++++----------------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/composer.json b/composer.json index f3ae59dc91..d3a125d305 100644 --- a/composer.json +++ b/composer.json @@ -43,13 +43,13 @@ "ext-sockets": "*", "appwrite/php-clamav": "1.1.*", "appwrite/php-runtimes": "0.11.*", - "utopia-php/abuse": "0.21.*", + "utopia-php/abuse": "0.22.*", "utopia-php/analytics": "0.2.*", - "utopia-php/audit": "0.23.*", + "utopia-php/audit": "0.24.*", "utopia-php/cache": "0.8.*", "utopia-php/cli": "0.13.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "dev-feat-relationships as 0.33.0", + "utopia-php/database": "dev-feat-relationships as 0.34.0", "utopia-php/preloader": "0.2.*", "utopia-php/domains": "1.1.*", "utopia-php/framework": "0.28.*", diff --git a/composer.lock b/composer.lock index 452237fe1d..b15f0a8d79 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "65ec111f49af9c148eb69dc16aec7a10", + "content-hash": "db3f68f41b0387401b964f65bad3053c", "packages": [ { "name": "adhocore/jwt", @@ -1808,23 +1808,23 @@ }, { "name": "utopia-php/abuse", - "version": "0.21.0", + "version": "0.22.1", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "7483068b192b27d698da9534c80091f69666d5eb" + "reference": "9360a026d18809e35523b254ab57e51055008203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/7483068b192b27d698da9534c80091f69666d5eb", - "reference": "7483068b192b27d698da9534c80091f69666d5eb", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/9360a026d18809e35523b254ab57e51055008203", + "reference": "9360a026d18809e35523b254ab57e51055008203", "shasum": "" }, "require": { "ext-curl": "*", "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "0.33.*" + "utopia-php/database": "0.34.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -1851,9 +1851,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.21.0" + "source": "https://github.com/utopia-php/abuse/tree/0.22.1" }, - "time": "2023-03-10T08:49:10+00:00" + "time": "2023-03-23T07:30:49+00:00" }, { "name": "utopia-php/analytics", @@ -1912,22 +1912,21 @@ }, { "name": "utopia-php/audit", - "version": "0.23.0", + "version": "0.24.1", "source": { "type": "git", "url": "https://github.com/utopia-php/audit.git", - "reference": "f16e893a22b93560d2af02afcb4761b3a940148a" + "reference": "237538b618506bbc0efc0e7884d49cdf52c20004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/audit/zipball/f16e893a22b93560d2af02afcb4761b3a940148a", - "reference": "f16e893a22b93560d2af02afcb4761b3a940148a", + "url": "https://api.github.com/repos/utopia-php/audit/zipball/237538b618506bbc0efc0e7884d49cdf52c20004", + "reference": "237538b618506bbc0efc0e7884d49cdf52c20004", "shasum": "" }, "require": { - "ext-pdo": "*", "php": ">=8.0", - "utopia-php/database": "0.33.*" + "utopia-php/database": "0.34.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -1954,9 +1953,9 @@ ], "support": { "issues": "https://github.com/utopia-php/audit/issues", - "source": "https://github.com/utopia-php/audit/tree/0.23.0" + "source": "https://github.com/utopia-php/audit/tree/0.24.1" }, - "time": "2023-03-10T08:51:26+00:00" + "time": "2023-03-23T07:29:11+00:00" }, { "name": "utopia-php/cache", @@ -2117,12 +2116,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "3dac815092cf8dc8feff79b7db461c67518db875" + "reference": "c1df0b712746b1283737956a1b0b143dca8f8610" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/3dac815092cf8dc8feff79b7db461c67518db875", - "reference": "3dac815092cf8dc8feff79b7db461c67518db875", + "url": "https://api.github.com/repos/utopia-php/database/zipball/c1df0b712746b1283737956a1b0b143dca8f8610", + "reference": "c1df0b712746b1283737956a1b0b143dca8f8610", "shasum": "" }, "require": { @@ -2167,7 +2166,7 @@ "issues": "https://github.com/utopia-php/database/issues", "source": "https://github.com/utopia-php/database/tree/feat-relationships" }, - "time": "2023-03-22T11:41:15+00:00" + "time": "2023-03-23T10:52:28+00:00" }, { "name": "utopia-php/domains", @@ -5662,8 +5661,8 @@ { "package": "utopia-php/database", "version": "dev-feat-relationships", - "alias": "0.33.0", - "alias_normalized": "0.33.0.0" + "alias": "0.34.0", + "alias_normalized": "0.34.0.0" } ], "minimum-stability": "stable", @@ -5693,5 +5692,5 @@ "platform-overrides": { "php": "8.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } From 947ad20badcaf25ee1e2b8c913642a9b378d043d Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 16:44:28 +1300 Subject: [PATCH 2/7] Pass attribute to select validator --- src/Appwrite/Utopia/Database/Validator/Queries/Base.php | 2 +- src/Appwrite/Utopia/Database/Validator/Query/Filter.php | 3 ++- src/Appwrite/Utopia/Database/Validator/Query/Select.php | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index e392560c86..2f7fd5d60d 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -62,12 +62,12 @@ class Base extends Queries ]); $validators = [ - new Select(), new Limit(), new Offset(), new Cursor(), new Filter($attributes), new Order($attributes), + new Select($attributes), ]; parent::__construct(...$validators); diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php index 5244e0756f..0be5ac7aad 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Filter.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Filter.php @@ -61,7 +61,7 @@ class Filter extends Base foreach ($values as $value) { $condition = match ($attributeType) { - Database::VAR_RELATIONSHIP => true, // Todo: ? + Database::VAR_RELATIONSHIP => true, Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING, default => gettype($value) === $attributeType }; @@ -91,6 +91,7 @@ class Filter extends Base // Validate method $method = $query->getMethod(); $attribute = $query->getAttribute(); + switch ($method) { case Query::TYPE_EQUAL: case Query::TYPE_NOTEQUAL: diff --git a/src/Appwrite/Utopia/Database/Validator/Query/Select.php b/src/Appwrite/Utopia/Database/Validator/Query/Select.php index f3ab1f6ae8..f1907f2e86 100644 --- a/src/Appwrite/Utopia/Database/Validator/Query/Select.php +++ b/src/Appwrite/Utopia/Database/Validator/Query/Select.php @@ -41,6 +41,8 @@ class Select extends Base foreach ($query->getValues() as $attribute) { if (\str_contains($attribute, '.')) { + // For relationships, just validate the top level. + // Utopia will validate each nested level during the recursive calls. $attribute = \explode('.', $attribute)[0]; } if (!isset($this->schema[$attribute]) && $attribute !== '*') { From b6cfe43681dfaf4970b2d802aba8ea56ad8dff4c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 16:46:02 +1300 Subject: [PATCH 3/7] Clean up --- app/controllers/api/databases.php | 7 +-- app/workers/databases.php | 61 +++++++++++-------- docker-compose.yml | 4 -- .../Response/Model/AttributeRelationship.php | 18 ++---- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 70f36242aa..eff7bc15cc 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -2750,12 +2750,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $filterQueries = Query::groupByType($queries)['filters']; - // todo: temporary fix until Utopia will be ready !!!! - foreach ($filterQueries as $key => $query) { - if (\str_contains($query->getAttribute(), '.')) { - unset($filterQueries[$key]); - } - } + if ($documentSecurity && !$valid) { $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT); diff --git a/app/workers/databases.php b/app/workers/databases.php index b1649d5568..c5743cc56b 100644 --- a/app/workers/databases.php +++ b/app/workers/databases.php @@ -95,26 +95,30 @@ class DatabaseV1 extends Worker $project = $dbForConsole->getDocument('projects', $projectId); try { - if ($type === Database::VAR_RELATIONSHIP) { - $relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']); - if ($relatedCollection->isEmpty()) { - throw new Exception('Missing collection'); - } - if ( - !$dbForProject->createRelationship( - collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), - relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), - type: $options['relationType'], - twoWay: $options['twoWay'], - id: $key, - twoWayKey: $options['twoWayKey'], - onDelete: $options['onDelete'], - ) - ) { - throw new Exception('Failed to create Attribute'); - } - } elseif (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) { - throw new Exception('Failed to create Attribute'); + switch ($type) { + case Database::VAR_RELATIONSHIP: + $relatedCollection = $dbForProject->getDocument('database_' . $database->getInternalId(), $options['relatedCollection']); + if ($relatedCollection->isEmpty()) { + throw new Exception('Collection not found'); + } + if ( + !$dbForProject->createRelationship( + collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), + relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), + type: $options['relationType'], + twoWay: $options['twoWay'], + id: $key, + twoWayKey: $options['twoWayKey'], + onDelete: $options['onDelete'], + ) + ) { + throw new Exception('Failed to create Attribute'); + } + break; + default: + if (!$dbForProject->createAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key, $type, $size, $required, $default, $signed, $array, $format, $formatOptions, $filters)) { + throw new Exception('Failed to create Attribute'); + } } $dbForProject->updateDocument('attributes', $attribute->getId(), $attribute->setAttribute('status', 'available')); @@ -151,6 +155,7 @@ class DatabaseV1 extends Worker * @param Document $collection * @param Document $attribute * @param string $projectId + * @throws Throwable */ protected function deleteAttribute(Document $database, Document $collection, Document $attribute, string $projectId): void { @@ -177,12 +182,16 @@ class DatabaseV1 extends Worker try { if ($status !== 'failed') { - if ($type === Database::VAR_RELATIONSHIP) { - if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { - throw new Exception('Failed to delete Attribute'); - } - } elseif (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { - throw new Exception('Failed to delete Attribute'); + switch ($type) { + case Database::VAR_RELATIONSHIP: + if (!$dbForProject->deleteRelationship('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { + throw new Exception('Failed to delete Attribute'); + } + break; + default: + if (!$dbForProject->deleteAttribute('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $key)) { + throw new Exception('Failed to delete Attribute'); + } } } $dbForProject->deleteDocument('attributes', $attribute->getId()); diff --git a/docker-compose.yml b/docker-compose.yml index 8eb5862d8d..8adb19e375 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,8 +83,6 @@ services: - ./docs:/usr/src/code/docs - ./public:/usr/src/code/public - ./src:/usr/src/code/src - - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework - - ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database depends_on: - mariadb - redis @@ -345,8 +343,6 @@ services: volumes: - ./app:/usr/src/code/app - ./src:/usr/src/code/src - - ./vendor/utopia-php/database:/usr/src/code/vendor/utopia-php/database - - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework depends_on: - redis - mariadb diff --git a/src/Appwrite/Utopia/Response/Model/AttributeRelationship.php b/src/Appwrite/Utopia/Response/Model/AttributeRelationship.php index 71b2039aea..d743eccf83 100644 --- a/src/Appwrite/Utopia/Response/Model/AttributeRelationship.php +++ b/src/Appwrite/Utopia/Response/Model/AttributeRelationship.php @@ -11,12 +11,6 @@ class AttributeRelationship extends Attribute parent::__construct(); $this - ->addRule('default', [ - 'type' => self::TYPE_STRING, - 'description' => 'Default value for attribute when not provided. Only null is optional', - 'default' => null, - 'example' => '', - ]) ->addRule('relatedCollection', [ 'type' => self::TYPE_STRING, 'description' => 'The Id of the related collection', @@ -25,26 +19,26 @@ class AttributeRelationship extends Attribute ]) ->addRule('relationType', [ 'type' => self::TYPE_STRING, - 'description' => 'The type of the relationship ', - 'default' => null, + 'description' => 'The type of the relationship', + 'default' => '', 'example' => 'oneToOne|oneToMany|manyToOne|manyToMany', ]) ->addRule('twoWay', [ 'type' => self::TYPE_BOOLEAN, 'description' => 'Is the relationship two-way?', - 'default' => null, - 'example' => 'relationship', + 'default' => false, + 'example' => false, ]) ->addRule('twoWayKey', [ 'type' => self::TYPE_STRING, 'description' => 'The key of the two-way relationship', - 'default' => null, + 'default' => '', 'example' => 'string', ]) ->addRule('onDelete', [ 'type' => self::TYPE_STRING, 'description' => 'Action to take on related documents when parent document is deleted', - 'default' => null, + 'default' => 'restrict', 'example' => 'restrict|cascade|setNull', ]) ; From 2775a92cdf3a04f5c6d0bfd84f0497da0d1afca7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 16:48:50 +1300 Subject: [PATCH 4/7] Add more tests --- .../e2e/Services/Databases/DatabasesBase.php | 374 +++++++++++++++++- .../Databases/DatabasesCustomServerTest.php | 2 +- 2 files changed, 357 insertions(+), 19 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 4ba450ebea..6c2f7a7226 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -234,11 +234,12 @@ trait DatabasesBase } /** - * @depends testCreateAttributes + * @depends testCreateDatabase */ - public function testRelations(array $data): array + public function testOneToOneRelationship(array $data): array { $databaseId = $data['databaseId']; + $person = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -274,7 +275,7 @@ trait DatabasesBase $this->assertEquals(201, $library['headers']['status-code']); - $fullname = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/string', array_merge([ + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/string', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -407,8 +408,24 @@ trait DatabasesBase $this->assertArrayNotHasKey('library', $person1['body']); + return [ + 'databaseId' => $databaseId, + 'personCollection' => $person['body']['$id'], + 'libraryCollection' => $library['body']['$id'], + ]; + } + + /** + * @depends testOneToOneRelationship + */ + public function testOneToManyRelationship(array $data): array + { + $databaseId = $data['databaseId']; + $personCollection = $data['personCollection']; + $libraryCollection = $data['libraryCollection']; + // One person can own several libraries - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([ + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/relationship', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -422,7 +439,7 @@ trait DatabasesBase sleep(1); - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/libraries", array_merge([ + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -439,7 +456,7 @@ trait DatabasesBase $this->assertEquals('person', $attribute['body']['twoWayKey']); $this->assertEquals('restrict', $attribute['body']['onDelete']); - $person2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ + $person2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ @@ -474,7 +491,11 @@ trait DatabasesBase ] ]); - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $person2['body']['$id'], array_merge([ + $this->assertEquals(201, $person2['headers']['status-code']); + $this->assertArrayHasKey('libraries', $person2['body']); + $this->assertEquals(2, count($person2['body']['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents/' . $person2['body']['$id'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -484,7 +505,7 @@ trait DatabasesBase $this->assertArrayHasKey('libraries', $response['body']); $this->assertEquals(2, count($response['body']['libraries'])); - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $library['body']['$id'] . '/documents/library11', array_merge([ + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $libraryCollection . '/documents/library11', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -493,7 +514,7 @@ trait DatabasesBase $this->assertArrayHasKey('person', $response['body']); $this->assertEquals('person10', $response['body']['person']['$id']); - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/libraries/relationship', array_merge([ + $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/libraries/relationship', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -504,7 +525,7 @@ trait DatabasesBase $this->assertEquals(200, $response['headers']['status-code']); - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/libraries", array_merge([ + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] @@ -520,11 +541,311 @@ trait DatabasesBase $this->assertEquals(false, $attribute['body']['twoWay']); $this->assertEquals(Database::RELATION_MUTATE_CASCADE, $attribute['body']['onDelete']); - return ['databaseId' => $databaseId, 'personCollection' => $person['body']['$id']]; + return ['databaseId' => $databaseId, 'personCollection' => $personCollection]; } /** - * @depends testRelations + * @depends testCreateDatabase + */ + public function testManyToOneRelationship(array $data): array + { + $databaseId = $data['databaseId']; + + // Create album collection + $albums = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Albums', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create album name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create artist collection + $artists = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Artists', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create artist name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create relationship + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'artist', + 'relatedCollectionId' => $artists['body']['$id'], + 'type' => Database::RELATION_MANY_TO_ONE, + 'twoWayKey' => 'albums', + 'twoWay' => true, + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + $this->assertEquals('artist', $response['body']['key']); + $this->assertEquals('relationship', $response['body']['type']); + $this->assertEquals(false, $response['body']['required']); + $this->assertEquals(false, $response['body']['array']); + $this->assertEquals('manyToOne', $response['body']['relationType']); + $this->assertEquals(true, $response['body']['twoWay']); + $this->assertEquals('albums', $response['body']['twoWayKey']); + $this->assertEquals('restrict', $response['body']['onDelete']); + + sleep(1); // Wait for worker + + $permissions = [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + Permission::delete(Role::user($this->getUser()['$id'])), + ]; + + // Create album + $album = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'album1', + 'permissions' => $permissions, + 'data' => [ + 'name' => 'Album 1', + 'artist' => [ + '$id' => ID::unique(), + 'name' => 'Artist 1', + ], + ], + ]); + + $this->assertEquals(201, $album['headers']['status-code']); + $this->assertEquals('album1', $album['body']['$id']); + $this->assertEquals('Album 1', $album['body']['name']); + $this->assertEquals('Artist 1', $album['body']['artist']['name']); + $this->assertEquals($permissions, $album['body']['$permissions']); + $this->assertEquals($permissions, $album['body']['artist']['$permissions']); + + $album = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents/album1', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $album['headers']['status-code']); + $this->assertEquals('album1', $album['body']['$id']); + $this->assertEquals('Album 1', $album['body']['name']); + $this->assertEquals('Artist 1', $album['body']['artist']['name']); + $this->assertEquals($permissions, $album['body']['$permissions']); + $this->assertEquals($permissions, $album['body']['artist']['$permissions']); + + $artist = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/documents/' . $album['body']['artist']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $artist['headers']['status-code']); + $this->assertEquals('Artist 1', $artist['body']['name']); + $this->assertEquals($permissions, $artist['body']['$permissions']); + $this->assertEquals(1, count($artist['body']['albums'])); + $this->assertEquals('album1', $artist['body']['albums'][0]['$id']); + $this->assertEquals('Album 1', $artist['body']['albums'][0]['name']); + $this->assertEquals($permissions, $artist['body']['albums'][0]['$permissions']); + + return [ + 'databaseId' => $databaseId, + 'albumsCollection' => $albums['body']['$id'], + 'artistsCollection' => $artists['body']['$id'], + ]; + } + + /** + * @depends testCreateDatabase + */ + public function testManyToManyRelationship(array $data): array + { + $databaseId = $data['databaseId']; + + // Create sports collection + $sports = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Sports', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create sport name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create player collection + $players = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Players', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create player name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create relationship + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'players', + 'relatedCollectionId' => $players['body']['$id'], + 'type' => Database::RELATION_MANY_TO_MANY, + 'twoWayKey' => 'sports', + 'twoWay' => true, + 'onDelete' => Database::RELATION_MUTATE_SET_NULL, + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + $this->assertEquals('players', $response['body']['key']); + $this->assertEquals('relationship', $response['body']['type']); + $this->assertEquals(false, $response['body']['required']); + $this->assertEquals(false, $response['body']['array']); + $this->assertEquals('manyToMany', $response['body']['relationType']); + $this->assertEquals(true, $response['body']['twoWay']); + $this->assertEquals('sports', $response['body']['twoWayKey']); + $this->assertEquals('setNull', $response['body']['onDelete']); + + sleep(1); // Wait for worker + + $permissions = [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ]; + + // Create sport + $sport = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'sport1', + 'permissions' => $permissions, + 'data' => [ + 'name' => 'Sport 1', + 'players' => [ + [ + '$id' => 'player1', + 'name' => 'Player 1', + ], + [ + '$id' => 'player2', + 'name' => 'Player 2', + ] + ], + ], + ]); + + $this->assertEquals(201, $sport['headers']['status-code']); + $this->assertEquals('sport1', $sport['body']['$id']); + $this->assertEquals('Sport 1', $sport['body']['name']); + $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); + $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); + $this->assertEquals($permissions, $sport['body']['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); + + $sport = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents/sport1', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $sport['headers']['status-code']); + $this->assertEquals('sport1', $sport['body']['$id']); + $this->assertEquals('Sport 1', $sport['body']['name']); + $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); + $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); + $this->assertEquals($permissions, $sport['body']['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); + + $player = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/documents/' . $sport['body']['players'][0]['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $player['headers']['status-code']); + $this->assertEquals('Player 1', $player['body']['name']); + $this->assertEquals($permissions, $player['body']['$permissions']); + $this->assertEquals(1, count($player['body']['sports'])); + $this->assertEquals('sport1', $player['body']['sports'][0]['$id']); + $this->assertEquals('Sport 1', $player['body']['sports'][0]['name']); + $this->assertEquals($permissions, $player['body']['sports'][0]['$permissions']); + + return [ + 'databaseId' => $databaseId, + 'sportsCollection' => $sports['body']['$id'], + 'playersCollection' => $players['body']['$id'], + ]; + } + + /** + * @depends testOneToManyRelationship */ public function testValidateOperators(array $data): void { @@ -534,19 +855,37 @@ trait DatabasesBase ], $this->getHeaders()), [ 'queries' => [ 'isNotNull("$id")', - 'isNull("fullName")', - 'startsWith("fullName", "Stevie")', - 'endsWith("fullName", "Wonder")', + 'startsWith("fullName", ["Stevie"])', + 'endsWith("fullName", ["Wonder"])', 'between("$createdAt", ["1975-12-06", "2050-12-06"])', + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, count($response['body']['documents'])); + $this->assertEquals('person10', $response['body']['documents'][0]['$id']); + $this->assertEquals('Stevie Wonder', $response['body']['documents'][0]['fullName']); + $this->assertEquals(2, count($response['body']['documents'][0]['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'isNotNull("$id")', + 'isNull("fullName")', 'select(["fullName"])' ], ]); $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, count($response['body']['documents'])); + $this->assertEquals(null, $response['body']['documents'][0]['fullName']); + $this->assertArrayNotHasKey("libraries", $response['body']['documents'][0]); } /** - * @depends testRelations + * @depends testOneToManyRelationship */ public function testSelectsQueries(array $data): void { @@ -575,7 +914,6 @@ trait DatabasesBase $document = $response['body']['documents'][0]; $this->assertEquals(200, $response['headers']['status-code']); $this->assertArrayHasKey('libraries', $document); - // $this->assertArrayNotHasKey('fullName', $document); // todo:this should work? $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents/' . $document['$id'], array_merge([ 'content-type' => 'application/json', @@ -786,7 +1124,7 @@ trait DatabasesBase $this->assertEquals(null, $datetime['body']['default']); // wait for database worker to create attributes - sleep(30); + sleep(10); $stringResponse = $this->client->call(Client::METHOD_GET, $attributesPath . '/' . $string['body']['key'], array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php index c0ae14d81e..09f4d34d1c 100644 --- a/tests/e2e/Services/Databases/DatabasesCustomServerTest.php +++ b/tests/e2e/Services/Databases/DatabasesCustomServerTest.php @@ -1165,7 +1165,7 @@ class DatabasesCustomServerTest extends Scope $this->assertEquals(202, $attribute['headers']['status-code']); } - sleep(20); + sleep(10); $collection = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ 'content-type' => 'application/json', From 24d1163703fff653d09942576cc990e0a235a980 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 16:57:56 +1300 Subject: [PATCH 5/7] Fix params --- app/controllers/api/databases.php | 5 +++-- tests/e2e/Services/Databases/DatabasesBase.php | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index eff7bc15cc..3b549471fa 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -1529,10 +1529,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->label('sdk.response.model', Response::MODEL_ATTRIBUTE_RELATIONSHIP) ->param('databaseId', '', new UID(), 'Database ID.') ->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') - ->param('key', '', new Key(), 'Attribute Key.') ->param('relatedCollectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).') ->param('type', '', new WhiteList([Database::RELATION_ONE_TO_ONE, Database::RELATION_MANY_TO_ONE, Database::RELATION_MANY_TO_MANY, Database::RELATION_ONE_TO_MANY]), 'Relation type') ->param('twoWay', false, new Boolean(), 'Is Two Way?', true) + ->param('key', null, new Key(), 'Attribute Key.', true) ->param('twoWayKey', null, new Key(), 'Two Way Key', true) ->param('onDelete', Database::RELATION_MUTATE_RESTRICT, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option', true) ->inject('response') @@ -1542,10 +1542,10 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->action(function ( string $databaseId, string $collectionId, - string $key, string $relatedCollectionId, string $type, bool $twoWay, + ?string $key, ?string $twoWayKey, string $onDelete, Response $response, @@ -1553,6 +1553,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati EventDatabase $database, Event $events ) { + $key ??= $relatedCollectionId; $twoWayKey ??= $collectionId; $attribute = createAttribute( diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 6c2f7a7226..525ab4b8ca 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -292,9 +292,9 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'library', 'relatedCollectionId' => 'library', 'type' => Database::RELATION_ONE_TO_ONE, + 'key' => 'library', 'onDelete' => Database::RELATION_MUTATE_CASCADE, ]); @@ -430,11 +430,11 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'libraries', 'relatedCollectionId' => 'library', 'type' => Database::RELATION_ONE_TO_MANY, - 'twoWayKey' => 'person', 'twoWay' => true, + 'key' => 'libraries', + 'twoWayKey' => 'person', ]); sleep(1); @@ -609,11 +609,11 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'artist', 'relatedCollectionId' => $artists['body']['$id'], 'type' => Database::RELATION_MANY_TO_ONE, - 'twoWayKey' => 'albums', 'twoWay' => true, + 'key' => 'artist', + 'twoWayKey' => 'albums', ]); $this->assertEquals(202, $response['headers']['status-code']); @@ -754,11 +754,11 @@ trait DatabasesBase 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] ]), [ - 'key' => 'players', 'relatedCollectionId' => $players['body']['$id'], 'type' => Database::RELATION_MANY_TO_MANY, - 'twoWayKey' => 'sports', 'twoWay' => true, + 'key' => 'players', + 'twoWayKey' => 'sports', 'onDelete' => Database::RELATION_MUTATE_SET_NULL, ]); From 1885f9e87c3206db2ca10b42a434d0641726e912 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 18:08:50 +1300 Subject: [PATCH 6/7] Add graphql tests --- app/controllers/api/databases.php | 3 +- tests/e2e/Services/GraphQL/Base.php | 26 ++++++ .../Services/GraphQL/DatabaseServerTest.php | 91 +++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 3b549471fa..c44d5f55ce 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -302,7 +302,6 @@ function updateAttribute( $dbForProject->updateRelationship( collection: $collectionId, key: $key, - newTwoWayKey: $options['twoWayKey'], twoWay: $options['twoWay'], onDelete: $options['onDelete'], ); @@ -1533,7 +1532,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati ->param('type', '', new WhiteList([Database::RELATION_ONE_TO_ONE, Database::RELATION_MANY_TO_ONE, Database::RELATION_MANY_TO_MANY, Database::RELATION_ONE_TO_MANY]), 'Relation type') ->param('twoWay', false, new Boolean(), 'Is Two Way?', true) ->param('key', null, new Key(), 'Attribute Key.', true) - ->param('twoWayKey', null, new Key(), 'Two Way Key', true) + ->param('twoWayKey', null, new Key(), 'Two Way Attribute Key.', true) ->param('onDelete', Database::RELATION_MUTATE_RESTRICT, new WhiteList([Database::RELATION_MUTATE_CASCADE, Database::RELATION_MUTATE_RESTRICT, Database::RELATION_MUTATE_SET_NULL]), 'Constraints option', true) ->inject('response') ->inject('dbForProject') diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index ab2c8c08e4..faa3f09757 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -26,6 +26,8 @@ trait Base public static string $CREATE_IP_ATTRIBUTE = 'create_ip_attribute'; public static string $CREATE_ENUM_ATTRIBUTE = 'create_enum_attribute'; public static string $CREATE_DATETIME_ATTRIBUTE = 'create_datetime_attribute'; + + public static string $CREATE_RELATIONSHIP_ATTRIBUTE = 'create_relationship_attribute'; public static string $UPDATE_STRING_ATTRIBUTE = 'update_string_attribute'; public static string $UPDATE_INTEGER_ATTRIBUTE = 'update_integer_attribute'; public static string $UPDATE_FLOAT_ATTRIBUTE = 'update_float_attribute'; @@ -35,6 +37,8 @@ trait Base public static string $UPDATE_IP_ATTRIBUTE = 'update_ip_attribute'; public static string $UPDATE_ENUM_ATTRIBUTE = 'update_enum_attribute'; public static string $UPDATE_DATETIME_ATTRIBUTE = 'update_datetime_attribute'; + + public static string $UPDATE_RELATIONSHIP_ATTRIBUTE = 'update_relationship_attribute'; public static string $GET_ATTRIBUTES = 'get_attributes'; public static string $GET_ATTRIBUTE = 'get_attribute'; public static string $DELETE_ATTRIBUTE = 'delete_attribute'; @@ -460,6 +464,17 @@ trait Base array } }'; + case self::$CREATE_RELATIONSHIP_ATTRIBUTE: + return 'mutation createRelationshipAttribute($databaseId: String!, $collectionId: String!, $relatedCollectionId: String!, $type: String!, $twoWay: Boolean, $key: String, $twoWayKey: String, $onDelete: String){ + databasesCreateRelationshipAttribute(databaseId: $databaseId, collectionId: $collectionId, relatedCollectionId: $relatedCollectionId, type: $type, twoWay: $twoWay, key: $key, twoWayKey: $twoWayKey, onDelete: $onDelete) { + relatedCollection + relationType + twoWay + key + twoWayKey + onDelete + } + }'; case self::$UPDATE_STRING_ATTRIBUTE: return 'mutation updateStringAttribute($databaseId: String!, $collectionId: String!, $key: String!, $required: Boolean!, $default: String){ databasesUpdateStringAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, required: $required, default: $default) { @@ -528,6 +543,17 @@ trait Base default } }'; + case self::$UPDATE_RELATIONSHIP_ATTRIBUTE: + return 'mutation updateRelationshipAttribute($databaseId: String!, $collectionId: String!, $key: String!, $twoWay: Boolean, $onDelete: String){ + databasesUpdateRelationshipAttribute(databaseId: $databaseId, collectionId: $collectionId, key: $key, twoWay: $twoWay, onDelete: $onDelete) { + relatedCollection + relationType + twoWay + key + twoWayKey + onDelete + } + }'; case self::$CREATE_INDEX: return 'mutation createIndex($databaseId: String!, $collectionId: String!, $key: String!, $type: String!, $attributes: [String!]!, $orders: [String!]){ databasesCreateIndex(databaseId: $databaseId, collectionId: $collectionId, key: $key, type: $type, attributes: $attributes, orders: $orders) { diff --git a/tests/e2e/Services/GraphQL/DatabaseServerTest.php b/tests/e2e/Services/GraphQL/DatabaseServerTest.php index 4baec9a62b..19dddb3895 100644 --- a/tests/e2e/Services/GraphQL/DatabaseServerTest.php +++ b/tests/e2e/Services/GraphQL/DatabaseServerTest.php @@ -7,6 +7,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\Database\Database; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -75,9 +76,36 @@ class DatabaseServerTest extends Scope $collection = $collection['body']['data']['databasesCreateCollection']; $this->assertEquals('Actors', $collection['name']); + $gqlPayload = [ + 'query' => $query, + 'variables' => [ + 'databaseId' => $database['_id'], + 'collectionId' => 'movies', + 'name' => 'Movies', + 'documentSecurity' => false, + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::users()), + Permission::update(Role::users()), + Permission::delete(Role::users()), + ], + ] + ]; + + $collection2 = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $gqlPayload); + + $this->assertIsArray($collection2['body']['data']); + $this->assertArrayNotHasKey('errors', $collection2['body']); + $collection2 = $collection2['body']['data']['databasesCreateCollection']; + $this->assertEquals('Movies', $collection2['name']); + return [ 'database' => $database, 'collection' => $collection, + 'collection2' => $collection2, ]; } @@ -570,6 +598,69 @@ class DatabaseServerTest extends Scope return $data; } + /** + * @depends testCreateCollection + */ + public function testCreateRelationshipAttribute(array $data): array + { + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$CREATE_RELATIONSHIP_ATTRIBUTE); + $gqlPayload = [ + 'query' => $query, + 'variables' => [ + 'databaseId' => $data['database']['_id'], + 'collectionId' => $data['collection2']['_id'], // Movies + 'relatedCollectionId' => $data['collection']['_id'], // Actors + 'type' => Database::RELATION_ONE_TO_MANY, + 'twoWay' => true, + 'key' => 'actors', + 'twoWayKey' => 'movie' + ] + ]; + + $attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $gqlPayload); + + $this->assertArrayNotHasKey('errors', $attribute['body']); + $this->assertIsArray($attribute['body']['data']); + $this->assertIsArray($attribute['body']['data']['databasesCreateRelationshipAttribute']); + + return $data; + } + + /** + * @depends testCreateRelationshipAttribute + */ + public function testUpdateRelationshipAttribute(array $data): array + { + sleep(3); + + $projectId = $this->getProject()['$id']; + $query = $this->getQuery(self::$UPDATE_RELATIONSHIP_ATTRIBUTE); + $gqlPayload = [ + 'query' => $query, + 'variables' => [ + 'databaseId' => $data['database']['_id'], + 'collectionId' => $data['collection2']['_id'], + 'key' => 'actors', + 'twoWay' => false, + ] + ]; + + $attribute = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ], $this->getHeaders()), $gqlPayload); + + $this->assertArrayNotHasKey('errors', $attribute['body']); + $this->assertIsArray($attribute['body']['data']); + $this->assertIsArray($attribute['body']['data']['databasesUpdateRelationshipAttribute']); + + return $data; + } + /** * @depends testCreateCollection * @throws Exception From a48ce855c71cf66e25c7ccb26b6e4fe3b998cad4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 24 Mar 2023 18:56:49 +1300 Subject: [PATCH 7/7] Move tests --- .../e2e/Services/Databases/DatabasesBase.php | 1392 ++++++++--------- 1 file changed, 696 insertions(+), 696 deletions(-) diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index 525ab4b8ca..eb26bc883a 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -233,702 +233,6 @@ trait DatabasesBase return $data; } - /** - * @depends testCreateDatabase - */ - public function testOneToOneRelationship(array $data): array - { - $databaseId = $data['databaseId']; - - $person = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => 'person', - 'name' => 'person', - 'permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - Permission::create(Role::user($this->getUser()['$id'])), - ], - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $person['headers']['status-code']); - - $library = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => 'library', - 'name' => 'library', - 'permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::create(Role::user($this->getUser()['$id'])), - ], - 'documentSecurity' => true, - ]); - - $this->assertEquals(201, $library['headers']['status-code']); - - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'fullName', - 'size' => 255, - 'required' => false, - ]); - - sleep(1); // Wait for worker - - $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'relatedCollectionId' => 'library', - 'type' => Database::RELATION_ONE_TO_ONE, - 'key' => 'library', - 'onDelete' => Database::RELATION_MUTATE_CASCADE, - ]); - - sleep(1); // Wait for worker - - $libraryName = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $library['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'libraryName', - 'size' => 255, - 'required' => true, - ]); - - sleep(1); // Wait for worker - - $this->assertEquals(202, $libraryName['headers']['status-code']); - $this->assertEquals(202, $relation['headers']['status-code']); - $this->assertEquals('library', $relation['body']['key']); - $this->assertEquals('relationship', $relation['body']['type']); - $this->assertEquals('processing', $relation['body']['status']); - - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/library", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $attribute['headers']['status-code']); - $this->assertEquals('available', $attribute['body']['status']); - $this->assertEquals('library', $attribute['body']['key']); - $this->assertEquals('relationship', $attribute['body']['type']); - $this->assertEquals(false, $attribute['body']['required']); - $this->assertEquals(false, $attribute['body']['array']); - $this->assertEquals('oneToOne', $attribute['body']['relationType']); - $this->assertEquals(false, $attribute['body']['twoWay']); - $this->assertEquals('person', $attribute['body']['twoWayKey']); - $this->assertEquals(Database::RELATION_MUTATE_CASCADE, $attribute['body']['onDelete']); - - $person1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'library' => [ - '$id' => 'library1', - '$permissions' => [ - Permission::read(Role::any()), - ], - 'libraryName' => 'Library 1', - ], - ], - 'permissions' => [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ] - ]); - - $this->assertEquals('Library 1', $person1['body']['library']['libraryName']); - - $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'equal("library", "library1")', - 'select(["fullName","library.*"])'] - ]); - - $this->assertEquals(1, $documents['body']['total']); - $this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']); - $this->assertArrayHasKey('fullName', $documents['body']['documents'][0]); - - $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - Query::equal('library.libraryName', ['Library 1']), - ], - ]); - - $this->assertEquals(1, $documents['body']['total']); - $this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']); - - $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/library', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - sleep(1); - - $this->assertEquals(204, $response['headers']['status-code']); - - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/library", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(404, $attribute['headers']['status-code']); - - $person1 = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $person1['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertArrayNotHasKey('library', $person1['body']); - - return [ - 'databaseId' => $databaseId, - 'personCollection' => $person['body']['$id'], - 'libraryCollection' => $library['body']['$id'], - ]; - } - - /** - * @depends testOneToOneRelationship - */ - public function testOneToManyRelationship(array $data): array - { - $databaseId = $data['databaseId']; - $personCollection = $data['personCollection']; - $libraryCollection = $data['libraryCollection']; - - // One person can own several libraries - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/relationship', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'relatedCollectionId' => 'library', - 'type' => Database::RELATION_ONE_TO_MANY, - 'twoWay' => true, - 'key' => 'libraries', - 'twoWayKey' => 'person', - ]); - - sleep(1); - - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $attribute['headers']['status-code']); - $this->assertEquals('available', $attribute['body']['status']); - $this->assertEquals('libraries', $attribute['body']['key']); - $this->assertEquals('relationship', $attribute['body']['type']); - $this->assertEquals(false, $attribute['body']['required']); - $this->assertEquals(false, $attribute['body']['array']); - $this->assertEquals('oneToMany', $attribute['body']['relationType']); - $this->assertEquals(true, $attribute['body']['twoWay']); - $this->assertEquals('person', $attribute['body']['twoWayKey']); - $this->assertEquals('restrict', $attribute['body']['onDelete']); - - $person2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => 'person10', - 'data' => [ - 'fullName' => 'Stevie Wonder', - 'libraries' => [ - [ - '$id' => 'library10', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'libraryName' => 'Library 10', - ], - [ - '$id' => 'library11', - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ], - 'libraryName' => 'Library 11', - ] - ], - ], - 'permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::any()), - Permission::delete(Role::any()), - ] - ]); - - $this->assertEquals(201, $person2['headers']['status-code']); - $this->assertArrayHasKey('libraries', $person2['body']); - $this->assertEquals(2, count($person2['body']['libraries'])); - - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents/' . $person2['body']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayNotHasKey('$collection', $response['body']); - $this->assertArrayHasKey('libraries', $response['body']); - $this->assertEquals(2, count($response['body']['libraries'])); - - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $libraryCollection . '/documents/library11', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('person', $response['body']); - $this->assertEquals('person10', $response['body']['person']['$id']); - - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/libraries/relationship', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'twoWay' => false, - 'onDelete' => Database::RELATION_MUTATE_CASCADE, - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - - $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->assertEquals(200, $attribute['headers']['status-code']); - $this->assertEquals('available', $attribute['body']['status']); - $this->assertEquals('libraries', $attribute['body']['key']); - $this->assertEquals('relationship', $attribute['body']['type']); - $this->assertEquals(false, $attribute['body']['required']); - $this->assertEquals(false, $attribute['body']['array']); - $this->assertEquals('oneToMany', $attribute['body']['relationType']); - $this->assertEquals(false, $attribute['body']['twoWay']); - $this->assertEquals(Database::RELATION_MUTATE_CASCADE, $attribute['body']['onDelete']); - - return ['databaseId' => $databaseId, 'personCollection' => $personCollection]; - } - - /** - * @depends testCreateDatabase - */ - public function testManyToOneRelationship(array $data): array - { - $databaseId = $data['databaseId']; - - // Create album collection - $albums = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Albums', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - ], - ]); - - // Create album name attribute - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 255, - 'required' => true, - ]); - - // Create artist collection - $artists = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Artists', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - ], - ]); - - // Create artist name attribute - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 255, - 'required' => true, - ]); - - // Create relationship - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/relationship', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'relatedCollectionId' => $artists['body']['$id'], - 'type' => Database::RELATION_MANY_TO_ONE, - 'twoWay' => true, - 'key' => 'artist', - 'twoWayKey' => 'albums', - ]); - - $this->assertEquals(202, $response['headers']['status-code']); - $this->assertEquals('artist', $response['body']['key']); - $this->assertEquals('relationship', $response['body']['type']); - $this->assertEquals(false, $response['body']['required']); - $this->assertEquals(false, $response['body']['array']); - $this->assertEquals('manyToOne', $response['body']['relationType']); - $this->assertEquals(true, $response['body']['twoWay']); - $this->assertEquals('albums', $response['body']['twoWayKey']); - $this->assertEquals('restrict', $response['body']['onDelete']); - - sleep(1); // Wait for worker - - $permissions = [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ]; - - // Create album - $album = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => 'album1', - 'permissions' => $permissions, - 'data' => [ - 'name' => 'Album 1', - 'artist' => [ - '$id' => ID::unique(), - 'name' => 'Artist 1', - ], - ], - ]); - - $this->assertEquals(201, $album['headers']['status-code']); - $this->assertEquals('album1', $album['body']['$id']); - $this->assertEquals('Album 1', $album['body']['name']); - $this->assertEquals('Artist 1', $album['body']['artist']['name']); - $this->assertEquals($permissions, $album['body']['$permissions']); - $this->assertEquals($permissions, $album['body']['artist']['$permissions']); - - $album = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents/album1', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $album['headers']['status-code']); - $this->assertEquals('album1', $album['body']['$id']); - $this->assertEquals('Album 1', $album['body']['name']); - $this->assertEquals('Artist 1', $album['body']['artist']['name']); - $this->assertEquals($permissions, $album['body']['$permissions']); - $this->assertEquals($permissions, $album['body']['artist']['$permissions']); - - $artist = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/documents/' . $album['body']['artist']['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $artist['headers']['status-code']); - $this->assertEquals('Artist 1', $artist['body']['name']); - $this->assertEquals($permissions, $artist['body']['$permissions']); - $this->assertEquals(1, count($artist['body']['albums'])); - $this->assertEquals('album1', $artist['body']['albums'][0]['$id']); - $this->assertEquals('Album 1', $artist['body']['albums'][0]['name']); - $this->assertEquals($permissions, $artist['body']['albums'][0]['$permissions']); - - return [ - 'databaseId' => $databaseId, - 'albumsCollection' => $albums['body']['$id'], - 'artistsCollection' => $artists['body']['$id'], - ]; - } - - /** - * @depends testCreateDatabase - */ - public function testManyToManyRelationship(array $data): array - { - $databaseId = $data['databaseId']; - - // Create sports collection - $sports = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Sports', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - ], - ]); - - // Create sport name attribute - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 255, - 'required' => true, - ]); - - // Create player collection - $players = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'collectionId' => ID::unique(), - 'name' => 'Players', - 'documentSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - ], - ]); - - // Create player name attribute - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'name', - 'size' => 255, - 'required' => true, - ]); - - // Create relationship - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/relationship', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'relatedCollectionId' => $players['body']['$id'], - 'type' => Database::RELATION_MANY_TO_MANY, - 'twoWay' => true, - 'key' => 'players', - 'twoWayKey' => 'sports', - 'onDelete' => Database::RELATION_MUTATE_SET_NULL, - ]); - - $this->assertEquals(202, $response['headers']['status-code']); - $this->assertEquals('players', $response['body']['key']); - $this->assertEquals('relationship', $response['body']['type']); - $this->assertEquals(false, $response['body']['required']); - $this->assertEquals(false, $response['body']['array']); - $this->assertEquals('manyToMany', $response['body']['relationType']); - $this->assertEquals(true, $response['body']['twoWay']); - $this->assertEquals('sports', $response['body']['twoWayKey']); - $this->assertEquals('setNull', $response['body']['onDelete']); - - sleep(1); // Wait for worker - - $permissions = [ - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - ]; - - // Create sport - $sport = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => 'sport1', - 'permissions' => $permissions, - 'data' => [ - 'name' => 'Sport 1', - 'players' => [ - [ - '$id' => 'player1', - 'name' => 'Player 1', - ], - [ - '$id' => 'player2', - 'name' => 'Player 2', - ] - ], - ], - ]); - - $this->assertEquals(201, $sport['headers']['status-code']); - $this->assertEquals('sport1', $sport['body']['$id']); - $this->assertEquals('Sport 1', $sport['body']['name']); - $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); - $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); - $this->assertEquals($permissions, $sport['body']['$permissions']); - $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); - $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); - - $sport = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents/sport1', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $sport['headers']['status-code']); - $this->assertEquals('sport1', $sport['body']['$id']); - $this->assertEquals('Sport 1', $sport['body']['name']); - $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); - $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); - $this->assertEquals($permissions, $sport['body']['$permissions']); - $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); - $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); - - $player = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/documents/' . $sport['body']['players'][0]['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - $this->assertEquals(200, $player['headers']['status-code']); - $this->assertEquals('Player 1', $player['body']['name']); - $this->assertEquals($permissions, $player['body']['$permissions']); - $this->assertEquals(1, count($player['body']['sports'])); - $this->assertEquals('sport1', $player['body']['sports'][0]['$id']); - $this->assertEquals('Sport 1', $player['body']['sports'][0]['name']); - $this->assertEquals($permissions, $player['body']['sports'][0]['$permissions']); - - return [ - 'databaseId' => $databaseId, - 'sportsCollection' => $sports['body']['$id'], - 'playersCollection' => $players['body']['$id'], - ]; - } - - /** - * @depends testOneToManyRelationship - */ - public function testValidateOperators(array $data): void - { - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'isNotNull("$id")', - 'startsWith("fullName", ["Stevie"])', - 'endsWith("fullName", ["Wonder"])', - 'between("$createdAt", ["1975-12-06", "2050-12-06"])', - ], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, count($response['body']['documents'])); - $this->assertEquals('person10', $response['body']['documents'][0]['$id']); - $this->assertEquals('Stevie Wonder', $response['body']['documents'][0]['fullName']); - $this->assertEquals(2, count($response['body']['documents'][0]['libraries'])); - - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'isNotNull("$id")', - 'isNull("fullName")', - 'select(["fullName"])' - ], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(1, count($response['body']['documents'])); - $this->assertEquals(null, $response['body']['documents'][0]['fullName']); - $this->assertArrayNotHasKey("libraries", $response['body']['documents'][0]); - } - - /** - * @depends testOneToManyRelationship - */ - public function testSelectsQueries(array $data): void - { - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'equal("fullName", "Stevie Wonder")', - 'select(["fullName"])' - ], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayNotHasKey('libraries', $response['body']['documents'][0]); - - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'select(["libraries.*"])', - ], - ]); - - $document = $response['body']['documents'][0]; - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('libraries', $document); - - $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents/' . $document['$id'], array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'queries' => [ - 'select(["fullName"])' - ], - ]); - - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertArrayHasKey('fullName', $response['body']); - $this->assertArrayNotHasKey('libraries', $response['body']); - } - /** * @depends testCreateAttributes */ @@ -3781,4 +3085,700 @@ trait DatabasesBase $this->assertEquals(202, $false['headers']['status-code']); } + + /** + * @depends testCreateDatabase + */ + public function testOneToOneRelationship(array $data): array + { + $databaseId = $data['databaseId']; + + $person = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => 'person', + 'name' => 'person', + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + Permission::delete(Role::user($this->getUser()['$id'])), + Permission::create(Role::user($this->getUser()['$id'])), + ], + 'documentSecurity' => true, + ]); + + $this->assertEquals(201, $person['headers']['status-code']); + + $library = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => 'library', + 'name' => 'library', + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + Permission::create(Role::user($this->getUser()['$id'])), + ], + 'documentSecurity' => true, + ]); + + $this->assertEquals(201, $library['headers']['status-code']); + + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'fullName', + 'size' => 255, + 'required' => false, + ]); + + sleep(1); // Wait for worker + + $relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'relatedCollectionId' => 'library', + 'type' => Database::RELATION_ONE_TO_ONE, + 'key' => 'library', + 'onDelete' => Database::RELATION_MUTATE_CASCADE, + ]); + + sleep(1); // Wait for worker + + $libraryName = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $library['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'libraryName', + 'size' => 255, + 'required' => true, + ]); + + sleep(1); // Wait for worker + + $this->assertEquals(202, $libraryName['headers']['status-code']); + $this->assertEquals(202, $relation['headers']['status-code']); + $this->assertEquals('library', $relation['body']['key']); + $this->assertEquals('relationship', $relation['body']['type']); + $this->assertEquals('processing', $relation['body']['status']); + + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/library", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $attribute['headers']['status-code']); + $this->assertEquals('available', $attribute['body']['status']); + $this->assertEquals('library', $attribute['body']['key']); + $this->assertEquals('relationship', $attribute['body']['type']); + $this->assertEquals(false, $attribute['body']['required']); + $this->assertEquals(false, $attribute['body']['array']); + $this->assertEquals('oneToOne', $attribute['body']['relationType']); + $this->assertEquals(false, $attribute['body']['twoWay']); + $this->assertEquals('person', $attribute['body']['twoWayKey']); + $this->assertEquals(Database::RELATION_MUTATE_CASCADE, $attribute['body']['onDelete']); + + $person1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => ID::unique(), + 'data' => [ + 'library' => [ + '$id' => 'library1', + '$permissions' => [ + Permission::read(Role::any()), + ], + 'libraryName' => 'Library 1', + ], + ], + 'permissions' => [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + Permission::delete(Role::user($this->getUser()['$id'])), + ] + ]); + + $this->assertEquals('Library 1', $person1['body']['library']['libraryName']); + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'equal("library", "library1")', + 'select(["fullName","library.*"])'] + ]); + + $this->assertEquals(1, $documents['body']['total']); + $this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']); + $this->assertArrayHasKey('fullName', $documents['body']['documents'][0]); + + $documents = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'equal("library.libraryName", ["Library 1"])', + ], + ]); + + $this->assertEquals(1, $documents['body']['total']); + $this->assertEquals('Library 1', $documents['body']['documents'][0]['library']['libraryName']); + + $response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/attributes/library', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + sleep(1); + + $this->assertEquals(204, $response['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$person['body']['$id']}/attributes/library", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(404, $attribute['headers']['status-code']); + + $person1 = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $person1['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertArrayNotHasKey('library', $person1['body']); + + return [ + 'databaseId' => $databaseId, + 'personCollection' => $person['body']['$id'], + 'libraryCollection' => $library['body']['$id'], + ]; + } + + /** + * @depends testOneToOneRelationship + */ + public function testOneToManyRelationship(array $data): array + { + $databaseId = $data['databaseId']; + $personCollection = $data['personCollection']; + $libraryCollection = $data['libraryCollection']; + + // One person can own several libraries + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'relatedCollectionId' => 'library', + 'type' => Database::RELATION_ONE_TO_MANY, + 'twoWay' => true, + 'key' => 'libraries', + 'twoWayKey' => 'person', + ]); + + sleep(1); + + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $attribute['headers']['status-code']); + $this->assertEquals('available', $attribute['body']['status']); + $this->assertEquals('libraries', $attribute['body']['key']); + $this->assertEquals('relationship', $attribute['body']['type']); + $this->assertEquals(false, $attribute['body']['required']); + $this->assertEquals(false, $attribute['body']['array']); + $this->assertEquals('oneToMany', $attribute['body']['relationType']); + $this->assertEquals(true, $attribute['body']['twoWay']); + $this->assertEquals('person', $attribute['body']['twoWayKey']); + $this->assertEquals('restrict', $attribute['body']['onDelete']); + + $person2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'person10', + 'data' => [ + 'fullName' => 'Stevie Wonder', + 'libraries' => [ + [ + '$id' => 'library10', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'libraryName' => 'Library 10', + ], + [ + '$id' => 'library11', + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'libraryName' => 'Library 11', + ] + ], + ], + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ] + ]); + + $this->assertEquals(201, $person2['headers']['status-code']); + $this->assertArrayHasKey('libraries', $person2['body']); + $this->assertEquals(2, count($person2['body']['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $personCollection . '/documents/' . $person2['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayNotHasKey('$collection', $response['body']); + $this->assertArrayHasKey('libraries', $response['body']); + $this->assertEquals(2, count($response['body']['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $libraryCollection . '/documents/library11', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('person', $response['body']); + $this->assertEquals('person10', $response['body']['person']['$id']); + + $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $personCollection . '/attributes/libraries/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'twoWay' => false, + 'onDelete' => Database::RELATION_MUTATE_CASCADE, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + + $attribute = $this->client->call(Client::METHOD_GET, "/databases/{$databaseId}/collections/{$personCollection}/attributes/libraries", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ])); + + $this->assertEquals(200, $attribute['headers']['status-code']); + $this->assertEquals('available', $attribute['body']['status']); + $this->assertEquals('libraries', $attribute['body']['key']); + $this->assertEquals('relationship', $attribute['body']['type']); + $this->assertEquals(false, $attribute['body']['required']); + $this->assertEquals(false, $attribute['body']['array']); + $this->assertEquals('oneToMany', $attribute['body']['relationType']); + $this->assertEquals(false, $attribute['body']['twoWay']); + $this->assertEquals(Database::RELATION_MUTATE_CASCADE, $attribute['body']['onDelete']); + + return ['databaseId' => $databaseId, 'personCollection' => $personCollection]; + } + + /** + * @depends testCreateDatabase + */ + public function testManyToOneRelationship(array $data): array + { + $databaseId = $data['databaseId']; + + // Create album collection + $albums = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Albums', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create album name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create artist collection + $artists = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Artists', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create artist name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create relationship + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'relatedCollectionId' => $artists['body']['$id'], + 'type' => Database::RELATION_MANY_TO_ONE, + 'twoWay' => true, + 'key' => 'artist', + 'twoWayKey' => 'albums', + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + $this->assertEquals('artist', $response['body']['key']); + $this->assertEquals('relationship', $response['body']['type']); + $this->assertEquals(false, $response['body']['required']); + $this->assertEquals(false, $response['body']['array']); + $this->assertEquals('manyToOne', $response['body']['relationType']); + $this->assertEquals(true, $response['body']['twoWay']); + $this->assertEquals('albums', $response['body']['twoWayKey']); + $this->assertEquals('restrict', $response['body']['onDelete']); + + sleep(1); // Wait for worker + + $permissions = [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + Permission::delete(Role::user($this->getUser()['$id'])), + ]; + + // Create album + $album = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'album1', + 'permissions' => $permissions, + 'data' => [ + 'name' => 'Album 1', + 'artist' => [ + '$id' => ID::unique(), + 'name' => 'Artist 1', + ], + ], + ]); + + $this->assertEquals(201, $album['headers']['status-code']); + $this->assertEquals('album1', $album['body']['$id']); + $this->assertEquals('Album 1', $album['body']['name']); + $this->assertEquals('Artist 1', $album['body']['artist']['name']); + $this->assertEquals($permissions, $album['body']['$permissions']); + $this->assertEquals($permissions, $album['body']['artist']['$permissions']); + + $album = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $albums['body']['$id'] . '/documents/album1', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $album['headers']['status-code']); + $this->assertEquals('album1', $album['body']['$id']); + $this->assertEquals('Album 1', $album['body']['name']); + $this->assertEquals('Artist 1', $album['body']['artist']['name']); + $this->assertEquals($permissions, $album['body']['$permissions']); + $this->assertEquals($permissions, $album['body']['artist']['$permissions']); + + $artist = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $artists['body']['$id'] . '/documents/' . $album['body']['artist']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $artist['headers']['status-code']); + $this->assertEquals('Artist 1', $artist['body']['name']); + $this->assertEquals($permissions, $artist['body']['$permissions']); + $this->assertEquals(1, count($artist['body']['albums'])); + $this->assertEquals('album1', $artist['body']['albums'][0]['$id']); + $this->assertEquals('Album 1', $artist['body']['albums'][0]['name']); + $this->assertEquals($permissions, $artist['body']['albums'][0]['$permissions']); + + return [ + 'databaseId' => $databaseId, + 'albumsCollection' => $albums['body']['$id'], + 'artistsCollection' => $artists['body']['$id'], + ]; + } + + /** + * @depends testCreateDatabase + */ + public function testManyToManyRelationship(array $data): array + { + $databaseId = $data['databaseId']; + + // Create sports collection + $sports = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Sports', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create sport name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create player collection + $players = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'collectionId' => ID::unique(), + 'name' => 'Players', + 'documentSecurity' => true, + 'permissions' => [ + Permission::create(Role::user($this->getUser()['$id'])), + Permission::read(Role::user($this->getUser()['$id'])), + ], + ]); + + // Create player name attribute + $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/attributes/string', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'key' => 'name', + 'size' => 255, + 'required' => true, + ]); + + // Create relationship + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/attributes/relationship', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'relatedCollectionId' => $players['body']['$id'], + 'type' => Database::RELATION_MANY_TO_MANY, + 'twoWay' => true, + 'key' => 'players', + 'twoWayKey' => 'sports', + 'onDelete' => Database::RELATION_MUTATE_SET_NULL, + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + $this->assertEquals('players', $response['body']['key']); + $this->assertEquals('relationship', $response['body']['type']); + $this->assertEquals(false, $response['body']['required']); + $this->assertEquals(false, $response['body']['array']); + $this->assertEquals('manyToMany', $response['body']['relationType']); + $this->assertEquals(true, $response['body']['twoWay']); + $this->assertEquals('sports', $response['body']['twoWayKey']); + $this->assertEquals('setNull', $response['body']['onDelete']); + + sleep(1); // Wait for worker + + $permissions = [ + Permission::read(Role::user($this->getUser()['$id'])), + Permission::update(Role::user($this->getUser()['$id'])), + ]; + + // Create sport + $sport = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'documentId' => 'sport1', + 'permissions' => $permissions, + 'data' => [ + 'name' => 'Sport 1', + 'players' => [ + [ + '$id' => 'player1', + 'name' => 'Player 1', + ], + [ + '$id' => 'player2', + 'name' => 'Player 2', + ] + ], + ], + ]); + + $this->assertEquals(201, $sport['headers']['status-code']); + $this->assertEquals('sport1', $sport['body']['$id']); + $this->assertEquals('Sport 1', $sport['body']['name']); + $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); + $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); + $this->assertEquals($permissions, $sport['body']['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); + + $sport = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $sports['body']['$id'] . '/documents/sport1', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $sport['headers']['status-code']); + $this->assertEquals('sport1', $sport['body']['$id']); + $this->assertEquals('Sport 1', $sport['body']['name']); + $this->assertEquals('Player 1', $sport['body']['players'][0]['name']); + $this->assertEquals('Player 2', $sport['body']['players'][1]['name']); + $this->assertEquals($permissions, $sport['body']['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][0]['$permissions']); + $this->assertEquals($permissions, $sport['body']['players'][1]['$permissions']); + + $player = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $players['body']['$id'] . '/documents/' . $sport['body']['players'][0]['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $player['headers']['status-code']); + $this->assertEquals('Player 1', $player['body']['name']); + $this->assertEquals($permissions, $player['body']['$permissions']); + $this->assertEquals(1, count($player['body']['sports'])); + $this->assertEquals('sport1', $player['body']['sports'][0]['$id']); + $this->assertEquals('Sport 1', $player['body']['sports'][0]['name']); + $this->assertEquals($permissions, $player['body']['sports'][0]['$permissions']); + + return [ + 'databaseId' => $databaseId, + 'sportsCollection' => $sports['body']['$id'], + 'playersCollection' => $players['body']['$id'], + ]; + } + + /** + * @depends testOneToManyRelationship + */ + public function testValidateOperators(array $data): void + { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'isNotNull("$id")', + 'startsWith("fullName", ["Stevie"])', + 'endsWith("fullName", ["Wonder"])', + 'between("$createdAt", ["1975-12-06", "2050-12-06"])', + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, count($response['body']['documents'])); + $this->assertEquals('person10', $response['body']['documents'][0]['$id']); + $this->assertEquals('Stevie Wonder', $response['body']['documents'][0]['fullName']); + $this->assertEquals(2, count($response['body']['documents'][0]['libraries'])); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'isNotNull("$id")', + 'isNull("fullName")', + 'select(["fullName"])' + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, count($response['body']['documents'])); + $this->assertEquals(null, $response['body']['documents'][0]['fullName']); + $this->assertArrayNotHasKey("libraries", $response['body']['documents'][0]); + } + + /** + * @depends testOneToManyRelationship + */ + public function testSelectsQueries(array $data): void + { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'equal("fullName", "Stevie Wonder")', + 'select(["fullName"])' + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayNotHasKey('libraries', $response['body']['documents'][0]); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'select(["libraries.*"])', + ], + ]); + + $document = $response['body']['documents'][0]; + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('libraries', $document); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['personCollection'] . '/documents/' . $document['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + 'select(["fullName"])' + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertArrayHasKey('fullName', $response['body']); + $this->assertArrayNotHasKey('libraries', $response['body']); + } }