Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ public function setHttpPath(string $path): 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];

return parent::setHttpPath($path);
}
Expand Down Expand Up @@ -200,11 +199,13 @@ protected function getCollectionsEventsContext(): string
* Remove configured removable attributes from a document.
* Used for relationship path handling to remove API-specific attributes.
*/
protected function removeReadonlyAttributes(Document $document): void
protected function removeReadonlyAttributes(Document|array $document): Document|array
{
foreach ($this->removableAttributes as $attribute) {
$document->removeAttribute($attribute);
\var_dump('Removing attribute: ' . $attribute);
unset($document[$attribute]);
}
return $document;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Fix helper: support Document|array, no var_dump, no fatal on Document

  • Using unset($document[$attribute]) on Utopia\Database\Document can fatal if ArrayAccess isn’t provided.
  • var_dump leaks internals and breaks responses.
  • Keep previous in-place mutation for Document; use unset for arrays; return the (possibly same) value.

Apply:

-    protected function removeReadonlyAttributes(Document|array $document): Document|array
-    {
-        foreach ($this->removableAttributes as $attribute) {
-            \var_dump('Removing attribute: ' . $attribute);
-            unset($document[$attribute]);
-        }
-        return $document;
-    }
+    protected function removeReadonlyAttributes(Document|array $document): Document|array
+    {
+        foreach ($this->removableAttributes as $attribute) {
+            if ($document instanceof Document) {
+                $document->removeAttribute($attribute);
+            } elseif (\is_array($document)) {
+                unset($document[$attribute]);
+            }
+        }
+        return $document;
+    }

Run to spot any remaining debug prints and audit call sites:


🏁 Script executed:

#!/bin/bash
# 1) Any stray var_dump in src
rg -nP --type=php '\bvar_dump\s*\(' src || true

# 2) All removeReadonlyAttributes call sites and surrounding usage
rg -nP --type=php -C2 '->removeReadonlyAttributes\s*\(' src

# Expectation:
# - No var_dump matches.
# - Calls passing arrays should capture the return value; calls passing Document can ignore it.

Length of output: 785


🏁 Script executed:

#!/bin/bash
# 1) Verify stray var_dump usage in src
rg -nP --type=php '\bvar_dump\s*\(' src || true

# 2) Find all call sites of removeReadonlyAttributes with context
rg -nP --type=php -C2 'removeReadonlyAttributes\s*\(' src || true

Length of output: 6268


Remove debug dumps and update removeReadonlyAttributes to handle Document safely

  • In src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php (line 205) and .../Update.php (line 244), remove the \var_dump(...) calls leaking internals.
  • Replace removeReadonlyAttributes body with:
 protected function removeReadonlyAttributes(Document|array $document): Document|array
 {
-    foreach ($this->removableAttributes as $attribute) {
-        \var_dump('Removing attribute: ' . $attribute);
-        unset($document[$attribute]);
-    }
+    foreach ($this->removableAttributes as $attribute) {
+        if ($document instanceof Document) {
+            $document->removeAttribute($attribute);
+        } elseif (is_array($document)) {
+            unset($document[$attribute]);
+        }
+    }
     return $document;
 }
  • After updating, re-run rg --type=php '\bvar_dump\s*\(' src to ensure no stray debug prints remain.
  • Audit all removeReadonlyAttributes(...) call sites to confirm array returns are captured and Document instances aren’t inadvertently re-wrapped.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected function removeReadonlyAttributes(Document|array $document): Document|array
{
foreach ($this->removableAttributes as $attribute) {
$document->removeAttribute($attribute);
\var_dump('Removing attribute: ' . $attribute);
unset($document[$attribute]);
}
return $document;
}
protected function removeReadonlyAttributes(Document|array $document): Document|array
{
foreach ($this->removableAttributes as $attribute) {
if ($document instanceof Document) {
$document->removeAttribute($attribute);
} elseif (is_array($document)) {
unset($document[$attribute]);
}
}
return $document;
}
🤖 Prompt for AI Agents
In
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php
around lines 202–209, remove the debugging \var_dump(...) calls and change
removeReadonlyAttributes so it does not assume the input is an array: iterate
removableAttributes and for each attribute, if $document is an array unset the
key, if $document is an instance of Document call the appropriate Document API
to remove that attribute (e.g. Document::removeAttribute or the project's
documented mutator) and leave other types untouched; ensure the method returns
the same type it received (Document or array). After editing, run rg --type=php
'\bvar_dump\s*\(' src to remove any remaining debug dumps and audit all call
sites of removeReadonlyAttributes to confirm callers correctly handle array
returns vs Document instances and do not re-wrap Document objects unnecessarily.


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ public function action(string $databaseId, string $collectionId, string|array $d
}
}

// Remove sequence if set
unset($document['$sequence']);
$data = $this->removeReadonlyAttributes($data);

$documents = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public function action(string $databaseId, string $collectionId, array $document
}

foreach ($documents as $key => $document) {
$document = $this->removeReadonlyAttributes($document);
$documents[$key] = new Document($document);
}
Comment on lines 102 to 105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove noisy debug output in removeReadonlyAttributes (can corrupt HTTP responses).

Calls here rely on a helper that currently var_dumps per attribute. This can leak into JSON responses and break clients. Replace the var_dump with a silent unset (or proper logger) in Action.php.

Apply in Action.php:

 protected function removeReadonlyAttributes(Document|array $document): Document|array
 {
     foreach ($this->removableAttributes as $attribute) {
-        \var_dump('Removing attribute: ' . $attribute);
-        unset($document[$attribute]);
+        if (isset($document[$attribute])) {
+            unset($document[$attribute]);
+        }
     }
     return $document;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
foreach ($documents as $key => $document) {
$document = $this->removeReadonlyAttributes($document);
$documents[$key] = new Document($document);
}
protected function removeReadonlyAttributes(Document|array $document): Document|array
{
foreach ($this->removableAttributes as $attribute) {
if (isset($document[$attribute])) {
unset($document[$attribute]);
}
}
return $document;
}
🤖 Prompt for AI Agents
In
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Upsert.php
around lines 102-105, the helper removeReadonlyAttributes used there currently
emits var_dump output which can leak into HTTP JSON responses; open Action.php
(where removeReadonlyAttributes is implemented), remove any var_dump/echo/print
statements, and instead silently unset the readonly keys from the document (e.g.
foreach readonly keys do unset($document[$key]) or unset($document->{$key}) as
appropriate) or replace those debug prints with a proper logger call
(logger->debug) that does not write to stdout, then return the cleaned document
so no debug text is emitted into responses.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ public function action(string $databaseId, string $documentId, string $collectio
$relation = new Document($relation);
}
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation);

$current = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getSequence() . '_collection_' . $relatedCollection->getSequence(), $relation->getId())
);
Expand All @@ -318,7 +320,6 @@ public function action(string $databaseId, string $documentId, string $collectio
$relation['$id'] = ID::unique();
}
} else {
$this->removeReadonlyAttributes($relation);
$relation->setAttribute('$collection', $relatedCollection->getId());
$type = Database::PERMISSION_UPDATE;
}
Expand Down Expand Up @@ -351,9 +352,6 @@ public function action(string $databaseId, string $documentId, string $collectio
}
}

// 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;

Expand All @@ -368,10 +366,11 @@ public function action(string $databaseId, string $documentId, string $collectio
}
}

$document = $this->removeReadonlyAttributes($document);

$document = new Document($document);
$setPermissions($document, $permissions);
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);

return $document;
}, $documents);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public function action(string $databaseId, string $collectionId, string $documen
}

$collectionsCache = [];

$this->processDocument(
database: $database,
collection: $collection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,14 @@ public function action(string $databaseId, string $collectionId, string $documen
$permissions = $document->getPermissions() ?? [];
}

// Remove sequence if set
unset($document['$sequence']);

$data['$id'] = $documentId;
$data['$permissions'] = $permissions;
$data = $this->removeReadonlyAttributes($data);
$newDocument = new Document($data);

$operations = 0;

$setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) {

$operations++;

$relationships = \array_filter(
Expand Down Expand Up @@ -208,11 +205,13 @@ public function action(string $databaseId, string $collectionId, string $documen
$relation = new Document($relation);
}
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation);

$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',
Expand Down Expand Up @@ -242,6 +241,8 @@ public function action(string $databaseId, string $collectionId, string $documen
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getSequence(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);

\var_dump($newDocument);

Comment on lines +234 to +235
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove debug output; var_dump corrupts HTTP responses and leaks data

var_dump will pollute JSON bodies and can expose sensitive info under load. Remove it.

Apply:

-        \var_dump($newDocument);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
\var_dump($newDocument);
🤖 Prompt for AI Agents
In
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php
around lines 244-245, remove the debug statement "var_dump($newDocument)"
because it corrupts HTTP responses and can leak sensitive data; delete that line
and, if necessary, replace it with a proper logger call (e.g., use the existing
logger to log non-sensitive info) or nothing at all to avoid contaminating JSON
output.

try {
$document = $dbForProject->withRequestTimestamp(
$requestTimestamp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public function action(string $databaseId, string $collectionId, string $documen

$data['$id'] = $documentId;
$data['$permissions'] = $permissions ?? [];
$data = $this->removeReadonlyAttributes($data);
$newDocument = new Document($data);
$operations = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove var_dump in removeReadonlyAttributes to prevent response corruption.

Same concern as bulk upsert path—helper emits output. Replace with silent unset (or logger).

Patch in Action.php:

-        \var_dump('Removing attribute: ' . $attribute);
-        unset($document[$attribute]);
+        if (isset($document[$attribute])) {
+            unset($document[$attribute]);
+        }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Upsert.php
around lines 173 to 175, the removeReadonlyAttributes helper currently emits
output (var_dump) which corrupts HTTP responses; update removeReadonlyAttributes
to stop printing—remove the var_dump and perform silent removal of readonly keys
(unset) or use the logger to record removed attributes instead, and ensure the
bulk upsert path uses the same silent behavior so no debug output is written to
the response.

Comment on lines 162 to 166
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify no lingering debug prints in helper

Past commits showed var_dump inside removeReadonlyAttributes. Please confirm none remain.

Also applies to: 207-208, 357-357


🏁 Script executed:

#!/usr/bin/env bash
# Scan the repo for accidental debug prints
rg -n 'var_dump\(|print_r\(' src/Appwrite/Platform/Modules/Databases/Http/Databases/ -C2
# Confirm helper implementation
rg -nPU '(?s)protected\s+function\s+removeReadonlyAttributes.*?\{' src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Action.php -C5

Length of output: 1493


Remove lingering debug print in Update.php
Delete the stray \var_dump($newDocument); at line 234 in
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php.
No other var_dump or print_r calls remain under src/Appwrite/Platform/Modules/Databases/Http/Databases.

🤖 Prompt for AI Agents
In
src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Update.php
around line 234, there is a stray debug call `\var_dump($newDocument);` that
must be removed; delete that line and run a quick grep to ensure no other
`var_dump` or `print_r` calls remain under
src/Appwrite/Platform/Modules/Databases/Http/Databases, keeping behavior
unchanged and no additional logging added.


Expand Down Expand Up @@ -213,11 +214,13 @@ public function action(string $databaseId, string $collectionId, string $documen
$relation = new Document($relation);
}
if ($relation instanceof Document) {
$relation = $this->removeReadonlyAttributes($relation);

$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',
Expand Down
69 changes: 69 additions & 0 deletions tests/e2e/Services/Databases/Legacy/DatabasesBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,7 @@ public function testCreateDocument(array $data): array
return $data;
}


/**
* @depends testCreateIndexes
*/
Expand Down Expand Up @@ -2211,6 +2212,49 @@ public function testUpsertDocument(array $data): void
$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:00Z';
$update['body']['$updatedAt'] = '2024-01-01T00:00:00Z';

$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']);
$this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']);
$this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']);
}
Comment on lines +2215 to 2264
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Strengthen assertions; avoid brittle “echo body back” payload

  • Add checks that nested relation readonly fields are also ignored.
  • Using the entire previous response body as the next request data risks coupling tests to incidental fields (e.g., $permissions). Prefer a minimal payload containing only the readonly fields you’re asserting against.

Apply this diff to extend assertions (keeping your current flow):

         $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']);
         $this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']);
         $this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']);
+        // Nested relation readonly attributes should also be ignored
+        $this->assertEquals('Library 3', $upserted['body']['library']['libraryName']);
+        $this->assertArrayHasKey('$createdAt', $upserted['body']['library']);
+        $this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['library']['$createdAt']);
+        $this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['library']['$updatedAt']);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 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:00Z';
$update['body']['$updatedAt'] = '2024-01-01T00:00:00Z';
$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']);
$this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']);
$this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']);
}
$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']);
$this->assertEquals($personNoPerm['body']['$createdAt'], $upserted['body']['$createdAt']);
$this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['$updatedAt']);
// Nested relation readonly attributes should also be ignored
$this->assertEquals('Library 3', $upserted['body']['library']['libraryName']);
$this->assertArrayHasKey('$createdAt', $upserted['body']['library']);
$this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['library']['$createdAt']);
$this->assertNotEquals('2024-01-01T00:00:00Z', $upserted['body']['library']['$updatedAt']);
}
🤖 Prompt for AI Agents
In tests/e2e/Services/Databases/Legacy/DatabasesBase.php around lines 2215-2258,
stop reusing the entire previous response body as the PUT payload and instead
send a minimal payload containing only the readonly fields you intend to test
(top-level readonly ids/sequence/timestamps and the nested relation readonly
fields under library), then assert that those readonly fields are ignored both
at top-level and inside the nested library object (compare
personNoPerm['body']['$id'], ['$collectionId'], ['$databaseId'], ['$sequence'],
['$createdAt'] and personNoPerm['body']['library']['$id'], ['libraryName' if
needed? — read-only fields only], ['$createdAt'], ['$updatedAt'] to the upserted
response), and also assert that incidental fields like $permissions are not
being relied upon or echoed back (i.e., not required in the request and
unchanged/absent in response). Replace the request payload with a small array of
only the readonly fields to be tested and add assertions verifying nested
relation readonly fields are ignored and updatedAt changes as before.

}

Expand Down Expand Up @@ -3000,6 +3044,31 @@ public function testUpdateDocument(array $data): array

$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+00:00',
'$updatedAt' => '2024-01-01T00:00:00+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('2024-01-01T00:00:00+00:00', $response['body']['$createdAt']);
$this->assertNotEquals('2024-01-01T00:00:00+00:00', $response['body']['$updatedAt']);
$this->assertNotEquals(9999, $response['body']['$sequence']);

return [];
}

Expand Down
Loading