diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php index 78df15b0c1..d1d0738990 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php @@ -27,9 +27,18 @@ abstract class Action extends AppwriteAction $this->context = ROWS; } - // Use the same helper method to ensure consistency $contextId = '$' . $this->getCollectionsEventsContext() . 'Id'; - $this->removableAttributes = ['$databaseId', $contextId, '$sequence']; + $this->removableAttributes = [ + '*' => [ + '$sequence', + '$databaseId', + $contextId, + ], + 'privileged' => [ + '$createdAt', + '$updatedAt', + ], + ]; return parent::setHttpPath($path); } @@ -200,11 +209,19 @@ abstract class Action extends AppwriteAction * Remove configured removable attributes from a document. * Used for relationship path handling to remove API-specific attributes. */ - protected function removeReadonlyAttributes(Document $document): void - { - foreach ($this->removableAttributes as $attribute) { - $document->removeAttribute($attribute); + protected function removeReadonlyAttributes( + Document|array $document, + bool $privileged = false, + ): Document|array { + foreach ($this->removableAttributes['*'] as $attribute) { + unset($document[$attribute]); } + if (!$privileged) { + foreach ($this->removableAttributes['privileged'] ?? [] as $attribute) { + unset($document[$attribute]); + } + } + return $document; } /** diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php index a9f9c3f76d..0f0ae14020 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Update.php @@ -127,8 +127,7 @@ class Update extends Action } } - // Remove sequence if set - unset($document['$sequence']); + $data = $this->removeReadonlyAttributes($data, privileged: true); $documents = []; diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php index a6f27637e3..3c6e5ddc57 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php @@ -100,6 +100,7 @@ class Upsert extends Action } foreach ($documents as $key => $document) { + $document = $this->removeReadonlyAttributes($document, privileged: true); $documents[$key] = new Document($document); } diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php index 04c90c4ec1..d274e1f128 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Create.php @@ -254,7 +254,7 @@ class Create extends Action $operations = 0; - $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, &$operations) { + $checkPermissions = function (Document $collection, Document $document, string $permission) use ($isAPIKey, $isPrivilegedUser, &$checkPermissions, $dbForProject, $database, &$operations) { $operations++; $documentSecurity = $collection->getAttribute('documentSecurity', false); @@ -307,6 +307,8 @@ class Create extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); + $current = Authorization::skip( fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId()) ); @@ -318,7 +320,6 @@ class Create extends Action $relation['$id'] = ID::unique(); } } else { - $this->removeReadonlyAttributes($relation); $relation->setAttribute('$collection', $relatedCollection->getId()); $type = Database::PERMISSION_UPDATE; } @@ -351,27 +352,12 @@ class Create extends Action } } - // Remove sequence if set - unset($document['$sequence']); - // Assign a unique ID if needed, otherwise use the provided ID. $document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId; - - // Allowing to add createdAt and updatedAt timestamps if server side(api key - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($document['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($document['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } - + $document = $this->removeReadonlyAttributes($document, $isAPIKey || $isPrivilegedUser); $document = new Document($document); $setPermissions($document, $permissions); $checkPermissions($collection, $document, Database::PERMISSION_CREATE); - return $document; }, $documents); diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php index 4ae1624f73..f34b4630c2 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Delete.php @@ -117,6 +117,7 @@ class Delete extends Action } $collectionsCache = []; + $this->processDocument( database: $database, collection: $collection, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php index 334bcb8448..556d14219f 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php @@ -109,16 +109,6 @@ class Update extends Action throw new Exception($this->getParentNotFoundException()); } - // Allowing to add createdAt and updatedAt timestamps if server side(api key) - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($data['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($data['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } // Read permission should not be required for update /** @var Document $document */ $document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); @@ -159,17 +149,14 @@ class Update extends Action $permissions = $document->getPermissions() ?? []; } - // Remove sequence if set - unset($document['$sequence']); - $data['$id'] = $documentId; $data['$permissions'] = $permissions; + $data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser); $newDocument = new Document($data); $operations = 0; - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { - + $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) { $operations++; $relationships = \array_filter( @@ -208,11 +195,13 @@ class Update extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() )); - $this->removeReadonlyAttributes($relation); + // Attribute $collection is required for Utopia. $relation->setAttribute( '$collection', @@ -242,6 +231,8 @@ class Update extends Action ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1)) ->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations); + \var_dump($newDocument); + try { $document = $dbForProject->withRequestTimestamp( $requestTimestamp, diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php index 6027a20c41..54b1cad950 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php @@ -121,7 +121,8 @@ class Upsert extends Action ]; $permissions = Permission::aggregate($permissions, $allowedPermissions); - // if no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) + + // If no permission, upsert permission from the old document if present (update scenario) else add default permission (create scenario) if (\is_null($permissions)) { $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $collection->getSequence(), $documentId)); if ($oldDocument->isEmpty()) { @@ -157,24 +158,14 @@ class Upsert extends Action } } } - // Allowing to add createdAt and updatedAt timestamps if server side(api key) - if (!$isAPIKey && !$isPrivilegedUser) { - if (isset($data['$createdAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$createdAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - - if (isset($data['$updatedAt'])) { - throw new Exception($this->getInvalidStructureException(), 'Attribute "$updatedAt" can not be modified. Please use a server SDK with an API key to modify server attributes.'); - } - } $data['$id'] = $documentId; $data['$permissions'] = $permissions ?? []; + $data = $this->removeReadonlyAttributes($data, $isAPIKey || $isPrivilegedUser); $newDocument = new Document($data); $operations = 0; - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { - + $setCollection = (function (Document $collection, Document $document) use ($isAPIKey, $isPrivilegedUser, &$setCollection, $dbForProject, $database, &$operations) { $operations++; $relationships = \array_filter( @@ -213,11 +204,13 @@ class Upsert extends Action $relation = new Document($relation); } if ($relation instanceof Document) { + $relation = $this->removeReadonlyAttributes($relation, $isAPIKey || $isPrivilegedUser); + $oldDocument = Authorization::skip(fn () => $dbForProject->getDocument( 'database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId() )); - $this->removeReadonlyAttributes($relation); + // Attribute $collection is required for Utopia. $relation->setAttribute( '$collection', diff --git a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php index 57ca550c18..9c8405cf18 100644 --- a/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php +++ b/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/XList.php @@ -176,7 +176,7 @@ class XList extends Action } // Check which removable attributes are explicitly requested - foreach ($this->removableAttributes as $attribute) { + foreach ($this->removableAttributes['*'] as $attribute) { if (\in_array($attribute, $values, true)) { $requestedAttributes[$attribute] = true; } @@ -186,7 +186,7 @@ class XList extends Action if (!$hasWildcard) { foreach ($documents as $document) { // Remove attributes that are not explicitly requested - foreach ($this->removableAttributes as $attribute) { + foreach ($this->removableAttributes['*'] as $attribute) { if (!isset($requestedAttributes[$attribute])) { $document->removeAttribute($attribute); } diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index ff25e799c1..a36c563747 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -212,7 +212,6 @@ class Migrations extends Action // set the errors back without trace $clonedMigrationDocument->setAttribute('errors', $errorMessages); - /** Trigger Realtime Events */ $queueForRealtime ->setProject($project) diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php index bd71272537..59864255ab 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesBase.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesBase.php @@ -1697,6 +1697,7 @@ trait DatabasesBase return $data; } + /** * @depends testCreateIndexes */ @@ -2211,6 +2212,55 @@ trait DatabasesBase $this->assertArrayHasKey('$permissions', $library3['body']); $this->assertCount(3, $library3['body']['$permissions']); $this->assertNotEmpty($library3['body']['$permissions']); + + // Readonly attributes are ignored + $personNoPerm = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + '$id' => 'some-other-id', + '$collectionId' => 'some-other-collection', + '$databaseId' => 'some-other-database', + '$createdAt' => '2024-01-01T00:00:00Z', + '$updatedAt' => '2024-01-01T00:00:00Z', + 'library' => [ + '$id' => 'library3', + 'libraryName' => 'Library 3', + '$createdAt' => '2024-01-01T00:00:00Z', + '$updatedAt' => '2024-01-01T00:00:00Z', + ], + ], + ]); + + $update = $personNoPerm; + $update['body']['$id'] = 'random'; + $update['body']['$sequence'] = 123; + $update['body']['$databaseId'] = 'random'; + $update['body']['$collectionId'] = 'random'; + $update['body']['$createdAt'] = '2024-01-01T00:00:00.000+00:00'; + $update['body']['$updatedAt'] = '2024-01-01T00:00:00.000+00:00'; + + $upserted = $this->client->call(Client::METHOD_PUT, '/databases/' . $databaseId . '/collections/' . $person['body']['$id'] . '/documents/' . $newPersonId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => $update['body'] + ]); + + $this->assertEquals(200, $upserted['headers']['status-code']); + $this->assertEquals($personNoPerm['body']['$id'], $upserted['body']['$id']); + $this->assertEquals($personNoPerm['body']['$collectionId'], $upserted['body']['$collectionId']); + $this->assertEquals($personNoPerm['body']['$databaseId'], $upserted['body']['$databaseId']); + $this->assertEquals($personNoPerm['body']['$sequence'], $upserted['body']['$sequence']); + + if ($this->getSide() === 'client') { + $this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']); + } else { + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$createdAt']); + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $upserted['body']['$updatedAt']); + } } } @@ -3000,6 +3050,37 @@ trait DatabasesBase $this->assertEquals(200, $response['headers']['status-code']); + // Test readonly attributes are ignored + $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/' . $id, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-timestamp' => DateTime::formatTz(DateTime::now()), + ], $this->getHeaders()), [ + 'data' => [ + '$id' => 'newId', + '$sequence' => 9999, + '$collectionId' => 'newCollectionId', + '$databaseId' => 'newDatabaseId', + '$createdAt' => '2024-01-01T00:00:00.000+00:00', + '$updatedAt' => '2024-01-01T00:00:00.000+00:00', + 'title' => 'Thor: Ragnarok', + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($id, $response['body']['$id']); + $this->assertEquals($data['moviesId'], $response['body']['$collectionId']); + $this->assertEquals($databaseId, $response['body']['$databaseId']); + $this->assertNotEquals(9999, $response['body']['$sequence']); + + if ($this->getSide() === 'client') { + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']); + $this->assertNotEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']); + } else { + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$createdAt']); + $this->assertEquals('2024-01-01T00:00:00.000+00:00', $response['body']['$updatedAt']); + } + return []; } @@ -4260,7 +4341,9 @@ trait DatabasesBase ] ]); if ($this->getSide() === 'client') { - $this->assertEquals($document['headers']['status-code'], 400); + $this->assertEquals($document['body']['title'], 'Again Updated Date Test'); + $this->assertNotEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); + $this->assertNotEquals($document['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); } else { $this->assertEquals($document['body']['title'], 'Again Updated Date Test'); $this->assertEquals($document['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); diff --git a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php index 23153e8f39..699a2b8f25 100644 --- a/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/Legacy/DatabasesCustomClientTest.php @@ -889,157 +889,4 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); } - public function testModifyCreatedAtUpdatedAtSingleDocument(): void - { - $database = $this->client->call(Client::METHOD_POST, '/databases', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database' - ]); - - $databaseId = $database['body']['$id']; - - $table = $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' => 'Test Table', - 'documentsecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ], - ]); - $collectionId = $table['body']['$id']; - - // Create string column - $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - - sleep(1); - - // Test 1: Try to create document with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Try to create document with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Try to create document with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 4: Create a valid document first - $validRow = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'documentId' => ID::unique(), - 'data' => [ - 'title' => 'Valid Movie' - ] - ]); - - $this->assertEquals(201, $validRow['headers']['status-code']); - $documentId = $validRow['body']['$id']; - - // Test 5: Try to update document with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 6: Try to update document with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 7: Try to update document with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collectionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - } } diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php index c57421c384..c2a1ee859d 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesBase.php @@ -4269,9 +4269,10 @@ trait DatabasesBase ]); if ($this->getSide() === 'client') { - $this->assertEquals($row['headers']['status-code'], 400); - } else { $this->assertEquals($row['body']['title'], 'Again Updated Date Test'); + $this->assertNotEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); + $this->assertNotEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); + } else { $this->assertEquals($row['body']['$createdAt'], DateTime::formatTz('2022-08-01 13:09:23.040')); $this->assertEquals($row['body']['$updatedAt'], DateTime::formatTz('2022-08-01 13:09:23.050')); diff --git a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php index 277771e2df..f986b5dd03 100644 --- a/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php +++ b/tests/e2e/Services/Databases/TablesDB/DatabasesCustomClientTest.php @@ -890,158 +890,4 @@ class DatabasesCustomClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); } - - public function testModifyCreatedAtUpdatedAtSingleRow(): void - { - $database = $this->client->call(Client::METHOD_POST, '/tablesdb', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ], [ - 'databaseId' => ID::unique(), - 'name' => 'Test Database' - ]); - - $databaseId = $database['body']['$id']; - - $table = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'tableId' => ID::unique(), - 'name' => 'Test Table', - 'rowSecurity' => true, - 'permissions' => [ - Permission::create(Role::user($this->getUser()['$id'])), - Permission::read(Role::user($this->getUser()['$id'])), - Permission::update(Role::user($this->getUser()['$id'])), - Permission::delete(Role::user($this->getUser()['$id'])), - ], - ]); - $tableId = $table['body']['$id']; - - // Create string column - $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ]), [ - 'key' => 'title', - 'size' => 256, - 'required' => true, - ]); - - sleep(1); - - // Test 1: Try to create row with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 2: Try to create row with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 3: Try to create row with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Test Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 4: Create a valid row first - $validRow = $this->client->call(Client::METHOD_POST, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'rowId' => ID::unique(), - 'data' => [ - 'title' => 'Valid Movie' - ] - ]); - - $this->assertEquals(201, $validRow['headers']['status-code']); - $rowId = $validRow['body']['$id']; - - // Test 5: Try to update row with $createdAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 6: Try to update row with $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Test 7: Try to update row with both $createdAt and $updatedAt - should return 400 - $response = $this->client->call(Client::METHOD_PATCH, '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'data' => [ - 'title' => 'Updated Movie', - '$createdAt' => '2000-01-01T10:00:00.000+00:00', - '$updatedAt' => '2000-01-01T10:00:00.000+00:00' - ] - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - // Cleanup - $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId . '/tables/' . $tableId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - - $this->client->call(Client::METHOD_DELETE, '/tablesdb/' . $databaseId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'] - ])); - } }