2021-03-02 09:56:18 +00:00
< ? php
namespace Tests\E2E\Services\Realtime ;
2021-12-06 12:03:12 +00:00
use CURLFile ;
2025-08-05 06:23:17 +00:00
use Exception ;
2025-12-24 08:11:31 +00:00
use Swoole\Coroutine ;
2021-12-06 12:03:12 +00:00
use Tests\E2E\Client ;
2021-03-02 09:56:18 +00:00
use Tests\E2E\Scopes\ProjectCustom ;
2024-03-06 17:34:21 +00:00
use Tests\E2E\Scopes\Scope ;
2021-03-02 09:56:18 +00:00
use Tests\E2E\Scopes\SideClient ;
2024-11-07 11:05:37 +00:00
use Tests\E2E\Services\Functions\FunctionsBase ;
2022-12-14 15:42:25 +00:00
use Utopia\Database\Helpers\ID ;
2022-12-14 16:04:06 +00:00
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2021-12-06 12:03:12 +00:00
use WebSocket\ConnectionException ;
2025-08-05 06:23:17 +00:00
use WebSocket\TimeoutException ;
2021-03-02 09:56:18 +00:00
class RealtimeCustomClientTest extends Scope
{
2024-11-07 11:05:37 +00:00
use FunctionsBase ;
2021-03-02 09:56:18 +00:00
use RealtimeBase ;
use ProjectCustom ;
use SideClient ;
2021-12-06 12:03:12 +00:00
2026-02-25 10:38:40 +00:00
/**
* Helper to create a team for membership tests .
*/
protected function createTeam () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$team = $this -> client -> call ( Client :: METHOD_POST , '/teams' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'teamId' => ID :: unique (),
'name' => 'Test Team ' . uniqid ()
]);
return [ 'teamId' => $team [ 'body' ][ '$id' ]];
}
2021-12-06 12:03:12 +00:00
public function testChannelParsing ()
{
$user = $this -> getUser ();
$userId = $user [ '$id' ] ? ? '' ;
$session = $user [ 'session' ] ? ? '' ;
2025-10-10 05:25:48 +00:00
$headers = [
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session
2021-12-06 12:03:12 +00:00
];
2025-05-09 09:41:14 +00:00
$client = $this -> getWebsocket ([ 'documents' ], $headers );
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
$client -> close ();
$client = $this -> getWebsocket ([ 'account' ], $headers );
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
$client -> close ();
2025-05-09 09:41:14 +00:00
$client = $this -> getWebsocket ([ 'account' , 'documents' , 'account.123' ], $headers );
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertCount ( 3 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
$client -> close ();
$client = $this -> getWebsocket ([
'account' ,
'files' ,
'files.1' ,
2025-05-09 09:41:14 +00:00
'collections' ,
2025-06-13 09:41:18 +00:00
'tables' ,
2025-05-09 09:41:14 +00:00
'collections.1.documents' ,
'collections.2.documents' ,
2025-06-13 09:41:18 +00:00
'tables.1.rows' ,
'tables.2.rows' ,
2025-05-09 09:41:14 +00:00
'documents' ,
2025-06-13 09:41:18 +00:00
'rows' ,
2025-05-09 09:41:14 +00:00
'collections.1.documents.1' ,
'collections.2.documents.2' ,
2025-06-13 09:41:18 +00:00
'tables.1.rows.1' ,
'tables.2.rows.2' ,
2021-12-06 12:03:12 +00:00
], $headers );
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
2025-06-13 09:41:18 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-02 08:42:13 +00:00
$this -> assertIsList ( $response [ 'data' ][ 'channels' ]);
$this -> assertTrue ( array_is_list ( $response [ 'data' ][ 'channels' ]));
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'files.1' , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
2025-06-13 09:41:18 +00:00
$this -> assertContains ( 'tables' , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'collections.1.documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections.2.documents' , $response [ 'data' ][ 'channels' ]);
2025-06-13 09:41:18 +00:00
$this -> assertContains ( 'tables.1.rows' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'tables.2.rows' , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections.1.documents.1' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections.2.documents.2' , $response [ 'data' ][ 'channels' ]);
2025-06-13 09:41:18 +00:00
$this -> assertContains ( 'tables.1.rows.1' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'tables.2.rows.2' , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
$client -> close ();
}
2024-11-07 11:05:37 +00:00
public function testPingPong ()
{
$client = $this -> getWebsocket ([ 'files' ], [
'origin' => 'http://localhost'
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
$client -> send ( \json_encode ([
'type' => 'ping'
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'pong' , $response [ 'type' ]);
$client -> close ();
}
2021-12-06 12:03:12 +00:00
public function testManualAuthentication ()
{
$user = $this -> getUser ();
$userId = $user [ '$id' ] ? ? '' ;
$session = $user [ 'session' ] ? ? '' ;
/**
* Test for SUCCESS
*/
$client = $this -> getWebsocket ([ 'account' ], [
'origin' => 'http://localhost'
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$client -> send ( \json_encode ([
'type' => 'authentication' ,
'data' => [
'session' => $session
]
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'response' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertEquals ( 'authentication' , $response [ 'data' ][ 'to' ]);
$this -> assertTrue ( $response [ 'data' ][ 'success' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test for FAILURE
*/
$client -> send ( \json_encode ([
'type' => 'authentication' ,
'data' => [
'session' => 'invalid_session'
]
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'error' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertEquals ( 1003 , $response [ 'data' ][ 'code' ]);
$this -> assertEquals ( 'Session is not valid.' , $response [ 'data' ][ 'message' ]);
$client -> send ( \json_encode ([
'type' => 'authentication' ,
'data' => []
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'error' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertEquals ( 1003 , $response [ 'data' ][ 'code' ]);
2026-05-13 13:22:14 +00:00
$this -> assertEquals ( 'Payload is not valid. Session is required' , $response [ 'data' ][ 'message' ]);
2021-12-06 12:03:12 +00:00
$client -> send ( \json_encode ([
'type' => 'unknown' ,
'data' => [
'session' => 'invalid_session'
]
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'error' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertEquals ( 1003 , $response [ 'data' ][ 'code' ]);
$this -> assertEquals ( 'Message type is not valid.' , $response [ 'data' ][ 'message' ]);
$client -> send ( \json_encode ([
'test' => '123' ,
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'error' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertEquals ( 1003 , $response [ 'data' ][ 'code' ]);
$this -> assertEquals ( 'Message format is not valid.' , $response [ 'data' ][ 'message' ]);
$client -> close ();
}
public function testConnectionPlatform ()
{
/**
* Test for FAILURE
*/
2025-05-09 09:41:14 +00:00
$client = $this -> getWebsocket ([ 'documents' ], [ 'origin' => 'http://appwrite.unknown' ]);
2021-12-06 12:03:12 +00:00
$payload = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $payload );
$this -> assertArrayHasKey ( 'data' , $payload );
$this -> assertEquals ( 'error' , $payload [ 'type' ]);
$this -> assertEquals ( 1008 , $payload [ 'data' ][ 'code' ]);
$this -> assertEquals ( 'Invalid Origin. Register your new client (appwrite.unknown) as a new Web platform on your project console dashboard' , $payload [ 'data' ][ 'message' ]);
2022-03-01 19:52:29 +00:00
\usleep ( 250000 ); // 250ms
2021-12-06 12:03:12 +00:00
$this -> expectException ( ConnectionException :: class ); // Check if server disconnnected client
$client -> close ();
}
public function testChannelAccount ()
{
$user = $this -> getUser ();
$userId = $user [ '$id' ] ? ? '' ;
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'account' ], [
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $userId , $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test Account Name Event
*/
$name = " Torsten Dittmann " ;
$this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'name' => $name
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .update.name " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update.name " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( $name , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Account Password Event
*/
$this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
2021-12-06 12:03:12 +00:00
]), [
'password' => 'new-password' ,
'oldPassword' => 'password' ,
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .update.password " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update.password " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( $name , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Account Email Update
*/
$this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
2021-12-06 12:03:12 +00:00
]), [
'email' => 'torsten@appwrite.io' ,
'password' => 'new-password' ,
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .update.email " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update.email " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'torsten@appwrite.io' , $response [ 'data' ][ 'payload' ][ 'email' ]);
/**
* Test Account Verification Create
*/
2022-04-13 12:39:31 +00:00
$verification = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
2021-12-06 12:03:12 +00:00
]), [
'url' => 'http://localhost/verification' ,
]);
2022-04-18 16:21:45 +00:00
$verificationId = $verification [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (verification) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2024-04-03 03:37:58 +00:00
$this -> assertArrayNotHasKey ( 'secret' , $response [ 'data' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.create' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.create' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .verification. { $verificationId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification. { $verificationId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification. { $verificationId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification. { $verificationId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
2026-02-25 10:38:40 +00:00
$lastEmail = $this -> getLastEmailByAddress ( 'torsten@appwrite.io' , function ( $email ) use ( $userId ) {
$this -> assertStringContainsString ( $userId , $email [ 'html' ]);
});
2025-03-29 13:29:36 +00:00
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
2026-02-25 10:38:40 +00:00
$verificationSecret = $tokens [ 'secret' ];
2021-12-06 12:03:12 +00:00
/**
* Test Account Verification Complete
*/
2022-04-13 12:39:31 +00:00
$verification = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification' , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
2021-12-06 12:03:12 +00:00
]), [
'userId' => $userId ,
2026-02-25 10:38:40 +00:00
'secret' => $verificationSecret ,
2021-12-06 12:03:12 +00:00
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $verification [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (verification) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .verification. { $verificationId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification. { $verificationId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .verification.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification. { $verificationId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification. { $verificationId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.verification.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
/**
* Test Acoount Prefs Update
*/
$this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
2021-12-06 12:03:12 +00:00
]), [
'prefs' => [
'prefKey1' => 'prefValue1' ,
'prefKey2' => 'prefValue2' ,
]
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .update.prefs " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update.prefs " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2022-10-27 17:32:49 +00:00
$createSession = function () use ( $projectId ) : array {
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => 'torsten@appwrite.io' ,
'password' => 'new-password' ,
]);
2023-12-08 23:17:13 +00:00
$sessionNew = $response [ 'cookies' ][ 'a_session_' . $projectId ];
2022-10-27 17:32:49 +00:00
$sessionNewId = $response [ 'body' ][ '$id' ];
return array ( " session " => $sessionNew , " sessionId " => $sessionNewId );
};
2021-12-06 12:03:12 +00:00
/**
* Test Account Session Create
*/
2022-10-27 17:32:49 +00:00
$sessionData = $createSession ();
2021-12-06 12:03:12 +00:00
2022-10-27 17:32:49 +00:00
$sessionNew = $sessionData [ 'session' ];
$sessionNewId = $sessionData [ 'sessionId' ];
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (sessions) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.create' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.create' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
/**
* Test Account Session Delete
*/
2022-04-18 16:21:45 +00:00
$this -> client -> call ( Client :: METHOD_DELETE , '/account/sessions/' . $sessionNewId , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $sessionNew ,
2021-12-06 12:03:12 +00:00
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (sessions) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.delete' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.delete' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } .delete " , $response [ 'data' ][ 'events' ]);
2022-10-27 17:32:49 +00:00
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
/**
* Test User Account Session Delete
*/
$sessionData = $createSession ();
$sessionNew = $sessionData [ 'session' ];
$sessionNewId = $sessionData [ 'sessionId' ];
$client -> receive (); // Receive the creation message and drop; this was tested earlier already
$this -> client -> call ( Client :: METHOD_DELETE , '/users/' . $userId . '/sessions/' . $sessionNewId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (sessions) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2022-10-27 17:32:49 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.delete' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.delete' , $response [ 'data' ][ 'channels' ]);
2022-10-27 17:32:49 +00:00
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } .delete " , $response [ 'data' ][ 'events' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions. { $sessionNewId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.sessions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
/**
* Test Account Create Recovery
*/
2022-04-13 12:39:31 +00:00
$recovery = $this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => 'torsten@appwrite.io' ,
'url' => 'http://localhost/recovery' ,
]);
2022-04-13 12:39:31 +00:00
$recoveryId = $recovery [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
2026-02-25 10:38:40 +00:00
$lastEmail = $this -> getLastEmailByAddress ( 'torsten@appwrite.io' , function ( $email ) use ( $userId ) {
$this -> assertStringContainsString ( $userId , $email [ 'html' ]);
$this -> assertStringContainsString ( 'recovery' , $email [ 'html' ]);
});
2025-03-29 13:50:27 +00:00
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
2026-02-25 10:38:40 +00:00
$recoverySecret = $tokens [ 'secret' ];
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (recovery) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.create' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.create' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .recovery. { $recoveryId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery. { $recoveryId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery. { $recoveryId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery. { $recoveryId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2026-02-25 10:38:40 +00:00
$recoveryResponse = $this -> client -> call ( Client :: METHOD_PUT , '/account/recovery' , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => $userId ,
2026-02-25 10:38:40 +00:00
'secret' => $recoverySecret ,
2021-12-06 12:03:12 +00:00
'password' => 'test-recovery' ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEquals ( 200 , $recoveryResponse [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-04-27 11:46:17 +00:00
// Nested user event (recovery) — must NOT suffix the account channels.
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'account' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'account.' . $userId , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:46:17 +00:00
$this -> assertNotContains ( 'account.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotContains ( 'account.' . $userId . '.update' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " users. { $userId } .recovery. { $recoveryId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery. { $recoveryId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } .recovery.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users. { $userId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery. { $recoveryId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery. { $recoveryId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.*.recovery.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " users.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$client -> close ();
}
public function testChannelDatabase ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
2025-05-09 09:41:14 +00:00
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
2025-12-22 12:31:00 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session ,
], null );
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
2022-06-22 10:51:49 +00:00
/**
* Test Database Create
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2022-08-14 10:33:36 +00:00
'databaseId' => ID :: unique (),
2022-06-22 10:51:49 +00:00
'name' => 'Actors DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
/**
* Test Collection Create
*/
2022-06-22 10:51:49 +00:00
$actors = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-05-09 09:41:14 +00:00
'collectionId' => ID :: unique (),
2021-12-06 12:03:12 +00:00
'name' => 'Actors' ,
2022-08-13 14:10:28 +00:00
'permissions' => [
2022-08-27 03:16:37 +00:00
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
2022-08-13 14:10:28 +00:00
],
2022-08-03 04:17:49 +00:00
'documentSecurity' => true ,
2021-12-16 18:12:06 +00:00
]);
2022-04-18 16:21:45 +00:00
$actorsId = $actors [ 'body' ][ '$id' ];
2021-12-16 18:12:06 +00:00
2022-06-22 10:51:49 +00:00
$name = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string' , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2021-12-17 10:41:26 +00:00
'key' => 'name' ,
2021-12-16 18:12:06 +00:00
'size' => 256 ,
'required' => true ,
]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 202 , $name [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'name' , $name [ 'body' ][ 'key' ]);
$this -> assertEquals ( 'string' , $name [ 'body' ][ 'type' ]);
$this -> assertEquals ( 256 , $name [ 'body' ][ 'size' ]);
$this -> assertTrue ( $name [ 'body' ][ 'required' ]);
2021-12-16 18:12:06 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $actorsId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2021-12-16 18:12:06 +00:00
/**
* Test Document Create
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-05-09 09:41:14 +00:00
'documentId' => ID :: unique (),
2021-12-16 18:12:06 +00:00
'data' => [
'name' => 'Chris Evans'
],
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
],
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
2022-04-18 16:21:45 +00:00
$documentId = $document [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
2025-10-06 14:43:36 +00:00
$this -> assertContains ( 'databases.' . $databaseId . '.tables.' . $actorsId . '.rows.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.tables.' . $actorsId . '.rows' , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-16 18:12:06 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Chris Evans' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-16 18:12:06 +00:00
/**
* Test Document Update
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_PATCH , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-05-09 09:41:14 +00:00
'documentId' => ID :: unique (),
2021-12-16 18:12:06 +00:00
'data' => [
'name' => 'Chris Evans 2'
],
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
],
2021-12-16 18:12:06 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
2025-10-06 14:43:36 +00:00
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-16 18:12:06 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Chris Evans 2' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-16 18:12:06 +00:00
/**
* Test Document Delete
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-05-09 09:41:14 +00:00
'documentId' => ID :: unique (),
2021-12-16 18:12:06 +00:00
'data' => [
'name' => 'Bradley Cooper'
],
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
],
2021-12-06 12:03:12 +00:00
]);
2021-12-16 18:12:06 +00:00
$client -> receive ();
2022-04-18 16:21:45 +00:00
$documentId = $document [ 'body' ][ '$id' ];
2022-06-22 10:51:49 +00:00
$this -> client -> call ( Client :: METHOD_DELETE , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
2025-10-06 14:43:36 +00:00
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows " , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-16 18:12:06 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Bradley Cooper' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-16 18:12:06 +00:00
2025-08-04 13:31:48 +00:00
// test bulk create
$documents = $this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
[
'$id' => ID :: unique (),
'name' => 'Scarlett Johansson' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
// Receive first document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// test bulk update
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
2025-08-05 06:23:17 +00:00
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: update ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: delete ( Role :: user ( $this -> getUser ()[ '$id' ])),
]
2025-08-04 13:31:48 +00:00
],
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive second document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive third document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Test bulk delete
$response = $this -> client -> call ( Client :: METHOD_DELETE , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive third document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
2025-09-03 14:06:24 +00:00
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// bulk upsert
$this -> client -> call ( Client :: METHOD_PUT , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-09-03 14:06:24 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
2025-08-04 13:31:48 +00:00
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
2021-12-16 18:12:06 +00:00
$client -> close ();
}
2025-08-04 13:31:48 +00:00
public function testChannelDatabaseBulkOperationMultipleClient ()
{
// user with api key will do operations and other valid users
$user1 = $this -> getUser ( true );
$user1Id = $user1 [ '$id' ];
$session = $user1 [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client1 = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2025-08-05 08:07:24 +00:00
$user2 = $this -> getUser ( true );
2025-08-04 13:31:48 +00:00
$user2Id = $user2 [ '$id' ];
$session = $user2 [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client2 = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
/**
* Test Database Create
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Actors DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
/**
* Test Collection Create
*/
$actors = $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' => 'Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$actorsId = $actors [ 'body' ][ '$id' ];
$name = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $name [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'name' , $name [ 'body' ][ 'key' ]);
$this -> assertEquals ( 'string' , $name [ 'body' ][ 'type' ]);
$this -> assertEquals ( 256 , $name [ 'body' ][ 'size' ]);
$this -> assertTrue ( $name [ 'body' ][ 'required' ]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $actorsId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-08-04 13:31:48 +00:00
// create
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Any' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
[
'$id' => ID :: unique (),
'name' => 'Users' ,
'$permissions' => [
Permission :: read ( Role :: users ()),
Permission :: update ( Role :: users ()),
Permission :: delete ( Role :: users ()),
],
],
[
'$id' => ID :: unique (),
'name' => 'User1' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user1Id )),
],
],
[
'$id' => ID :: unique (),
'name' => 'User2' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user2Id )),
],
],
[
'$id' => ID :: unique (),
2025-08-05 06:23:17 +00:00
'name' => 'User2' ,
2025-08-04 13:31:48 +00:00
'$permissions' => [
Permission :: read ( Role :: user ( $user2Id )),
],
]
],
]);
// Receive and assert for client1 - should receive 3 individual document events
for ( $i = 0 ; $i < 3 ; $i ++ ) {
$response1 = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response1 );
$this -> assertArrayHasKey ( 'data' , $response1 );
$this -> assertEquals ( 'event' , $response1 [ 'type' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response1 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response1 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response1 [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ][ '$permissions' ]);
}
// Receive and assert for client2 - should receive 4 individual document events
for ( $i = 0 ; $i < 4 ; $i ++ ) {
$response2 = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response2 );
$this -> assertArrayHasKey ( 'data' , $response2 );
$this -> assertEquals ( 'event' , $response2 [ 'type' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response2 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response2 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response2 [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ][ '$permissions' ]);
}
2025-08-05 06:23:17 +00:00
// Perform bulk update(making it only accessible by user1)
2025-08-04 13:31:48 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /databases/ { $databaseId } /collections/ { $actorsId } /documents/ " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
2025-08-05 06:23:17 +00:00
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user1Id )),
Permission :: update ( Role :: user ( $user1Id )),
Permission :: delete ( Role :: user ( $user1Id )),
]
2025-08-04 13:31:48 +00:00
],
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-08-05 06:23:17 +00:00
// Receive and assert for client1
for ( $i = 0 ; $i < 5 ; $i ++ ) {
2025-08-04 13:31:48 +00:00
$response1 = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response1 );
$this -> assertArrayHasKey ( 'data' , $response1 );
$this -> assertEquals ( 'event' , $response1 [ 'type' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response1 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response1 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response1 [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response1 [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response1 [ 'data' ][ 'payload' ]);
}
2025-08-05 06:23:17 +00:00
// client2 shouldn't receive any event and lead to timeout
try {
json_decode ( $client2 -> receive (), true );
$this -> fail ( 'Expected TimeoutException was not thrown.' );
} catch ( Exception $e ) {
$this -> assertInstanceOf ( TimeoutException :: class , $e );
}
// Perform bulk update(making it only accessible by user2)
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /databases/ { $databaseId } /collections/ { $actorsId } /documents/ " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user2Id )),
Permission :: update ( Role :: user ( $user2Id )),
Permission :: delete ( Role :: user ( $user2Id )),
]
],
]);
// Receive and assert for client2
for ( $i = 0 ; $i < 5 ; $i ++ ) {
$response2 = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response2 );
$this -> assertArrayHasKey ( 'data' , $response2 );
$this -> assertEquals ( 'event' , $response2 [ 'type' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response2 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response2 [ 'data' ][ 'channels' ]);
2025-08-05 06:23:17 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response2 [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response2 [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response2 [ 'data' ][ 'payload' ]);
}
// client1 shouldn't receive any event and lead to timeout
try {
json_decode ( $client1 -> receive (), true );
$this -> fail ( 'Expected TimeoutException was not thrown.' );
} catch ( Exception $e ) {
$this -> assertInstanceOf ( TimeoutException :: class , $e );
}
// Updating the permission for both the users
$response = $this -> client -> call ( Client :: METHOD_PATCH , " /databases/ { $databaseId } /collections/ { $actorsId } /documents/ " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: users ()),
Permission :: update ( Role :: users ()),
Permission :: delete ( Role :: users ()),
]
],
]);
// both user1 and user2 should receive the event
for ( $i = 0 ; $i < 5 ; $i ++ ) {
$response1 = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response1 );
$this -> assertArrayHasKey ( 'data' , $response1 );
$this -> assertEquals ( 'event' , $response1 [ 'type' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response1 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response1 [ 'data' ][ 'channels' ]);
2025-08-05 06:23:17 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response1 [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response1 [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response1 [ 'data' ][ 'payload' ]);
2025-08-04 13:31:48 +00:00
$response2 = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response2 );
$this -> assertArrayHasKey ( 'data' , $response2 );
$this -> assertEquals ( 'event' , $response2 [ 'type' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response2 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response2 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response2 [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response2 [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response2 [ 'data' ][ 'payload' ]);
}
// Perform bulk delete
$response = $this -> client -> call ( Client :: METHOD_DELETE , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-08-05 06:23:17 +00:00
// Receive and assert for client1
for ( $i = 0 ; $i < 5 ; $i ++ ) {
2025-08-04 13:31:48 +00:00
$response1 = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response1 );
$this -> assertArrayHasKey ( 'data' , $response1 );
$this -> assertEquals ( 'event' , $response1 [ 'type' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response1 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response1 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response1 [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response1 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response1 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response1 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response1 [ 'data' ][ 'payload' ][ '$permissions' ]);
}
2025-08-05 06:23:17 +00:00
// Receive and assert for client2
for ( $i = 0 ; $i < 5 ; $i ++ ) {
2025-08-04 13:31:48 +00:00
$response2 = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response2 );
$this -> assertArrayHasKey ( 'data' , $response2 );
$this -> assertEquals ( 'event' , $response2 [ 'type' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response2 [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response2 [ 'data' ][ 'channels' ]);
2025-08-04 13:31:48 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response2 [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response2 [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response2 [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response2 [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response2 [ 'data' ][ 'payload' ][ '$permissions' ]);
}
2025-09-03 14:23:20 +00:00
// bulk upsert
$this -> client -> call ( Client :: METHOD_PUT , " /databases/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user1Id )),
],
],
[
'$id' => ID :: unique (),
'name' => 'Thor' ,
'$permissions' => [
Permission :: read ( Role :: user ( $user2Id )),
],
]
],
]);
$response = json_decode ( $client1 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-09-03 14:23:20 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// client1 shouldnot receive more than 1 event
try {
json_decode ( json_decode ( $client1 -> receive (), true ));
$this -> fail ( 'Expected TimeoutException was not thrown.' );
} catch ( Exception $e ) {
$this -> assertInstanceOf ( TimeoutException :: class , $e );
}
$response = json_decode ( $client2 -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-09-03 14:23:20 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// client2 shouldnot receive more than 1 event
try {
json_decode ( json_decode ( $client2 -> receive (), true ));
$this -> fail ( 'Expected TimeoutException was not thrown.' );
} catch ( Exception $e ) {
$this -> assertInstanceOf ( TimeoutException :: class , $e );
}
2025-08-04 13:31:48 +00:00
$client1 -> close ();
$client2 -> close ();
}
2021-12-16 18:12:06 +00:00
public function testChannelDatabaseCollectionPermissions ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
2025-05-09 09:41:14 +00:00
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
2021-12-16 18:12:06 +00:00
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session
2021-12-16 18:12:06 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
2021-12-06 12:03:12 +00:00
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
2021-12-16 18:12:06 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
2022-06-22 10:51:49 +00:00
/**
* Test Database Create
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2022-08-14 10:33:36 +00:00
'databaseId' => ID :: unique (),
2022-06-22 10:51:49 +00:00
'name' => 'Actors DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
2021-12-16 18:12:06 +00:00
/**
* Test Collection Create
*/
2022-06-22 10:51:49 +00:00
$actors = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections' , array_merge ([
2021-12-16 18:12:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2025-05-09 09:41:14 +00:00
'collectionId' => ID :: unique (),
2021-12-16 18:12:06 +00:00
'name' => 'Actors' ,
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
]
2021-12-16 18:12:06 +00:00
]);
2021-12-06 12:03:12 +00:00
2022-04-18 16:21:45 +00:00
$actorsId = $actors [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
2022-06-22 10:51:49 +00:00
$name = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/string' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2021-12-16 16:10:01 +00:00
'key' => 'name' ,
2021-12-06 12:03:12 +00:00
'size' => 256 ,
'required' => true ,
]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 202 , $name [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'name' , $name [ 'body' ][ 'key' ]);
$this -> assertEquals ( 'string' , $name [ 'body' ][ 'type' ]);
$this -> assertEquals ( 256 , $name [ 'body' ][ 'size' ]);
$this -> assertTrue ( $name [ 'body' ][ 'required' ]);
2021-12-06 12:03:12 +00:00
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $actorsId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2021-12-06 12:03:12 +00:00
/**
* Test Document Create
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-05-09 09:41:14 +00:00
'documentId' => ID :: unique (),
2021-12-06 12:03:12 +00:00
'data' => [
'name' => 'Chris Evans'
],
2022-08-03 04:17:49 +00:00
'permissions' => [],
2021-12-06 12:03:12 +00:00
]);
2022-04-18 16:21:45 +00:00
$documentId = $document [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Chris Evans' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-06 12:03:12 +00:00
/**
* Test Document Update
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_PATCH , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'data' => [
'name' => 'Chris Evans 2'
],
2022-08-03 04:17:49 +00:00
'permissions' => [],
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Chris Evans 2' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-06 12:03:12 +00:00
/**
* Test Document Delete
*/
2022-06-22 10:51:49 +00:00
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2025-05-09 09:41:14 +00:00
'documentId' => ID :: unique (),
2021-12-06 12:03:12 +00:00
'data' => [
'name' => 'Bradley Cooper'
],
2022-08-03 04:17:49 +00:00
'permissions' => [],
2021-12-06 12:03:12 +00:00
]);
2022-04-18 16:21:45 +00:00
$documentId = $document [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$client -> receive ();
2022-06-22 10:51:49 +00:00
$this -> client -> call ( Client :: METHOD_DELETE , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2025-05-09 09:41:14 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $documentId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
2022-06-22 10:51:49 +00:00
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'Bradley Cooper' , $response [ 'data' ][ 'payload' ][ 'name' ]);
2021-12-06 12:03:12 +00:00
$client -> close ();
}
public function testChannelFiles ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'files' ], [
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
2021-12-14 08:01:17 +00:00
/**
* Test Bucket Create
*/
$bucket1 = $this -> client -> call ( Client :: METHOD_POST , '/storage/buckets' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2022-08-14 10:33:36 +00:00
'bucketId' => ID :: unique (),
2021-12-14 08:01:17 +00:00
'name' => 'Bucket 1' ,
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
]
2021-12-14 08:01:17 +00:00
]);
2022-04-18 16:21:45 +00:00
$bucketId = $bucket1 [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
/**
* Test File Create
*/
2022-04-18 16:21:45 +00:00
$file = $this -> client -> call ( Client :: METHOD_POST , '/storage/buckets/' . $bucketId . '/files' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'multipart/form-data' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2022-08-14 10:33:36 +00:00
'fileId' => ID :: unique (),
2021-12-06 12:03:12 +00:00
'file' => new CURLFile ( realpath ( __DIR__ . '/../../../resources/logo.png' ), 'image/png' , 'logo.png' ),
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
],
2021-12-06 12:03:12 +00:00
]);
2022-04-18 16:21:45 +00:00
$fileId = $file [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'files.create' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.create " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .create " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2022-04-18 16:21:45 +00:00
$fileId = $file [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
/**
* Test File Update
*/
2022-04-18 16:21:45 +00:00
$this -> client -> call ( Client :: METHOD_PUT , '/storage/buckets/' . $bucketId . '/files/' . $fileId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2022-08-03 04:17:49 +00:00
'permissions' => [
2022-08-14 05:21:11 +00:00
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
2022-08-03 04:17:49 +00:00
],
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'files.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.update " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .update " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
/**
* Test File Delete
*/
2022-04-18 16:21:45 +00:00
$this -> client -> call ( Client :: METHOD_DELETE , '/storage/buckets/' . $bucketId . '/files/' . $fileId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'files' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'files.delete' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.delete " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .delete " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } .files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets. { $bucketId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files. { $fileId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.*.files.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " buckets.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$client -> close ();
}
public function testChannelExecutions ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
2026-03-03 22:47:28 +00:00
$client = $this -> getWebsocket (
channels : [ 'executions' ],
headers : [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
],
timeout : 10
);
2021-12-06 12:03:12 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'executions' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test Functions Create
*/
$function = $this -> client -> call ( Client :: METHOD_POST , '/functions' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
], [
2022-08-14 10:33:36 +00:00
'functionId' => ID :: unique (),
2025-07-09 14:58:55 +00:00
'name' => 'Test timeout execution' ,
2022-08-03 04:17:49 +00:00
'execute' => [ 'users' ],
2025-07-09 14:58:55 +00:00
'runtime' => 'node-22' ,
'entrypoint' => 'index.js' ,
2021-12-06 12:03:12 +00:00
'timeout' => 10 ,
]);
$functionId = $function [ 'body' ][ '$id' ] ? ? '' ;
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 201 , $function [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $function [ 'body' ][ '$id' ]);
2022-04-18 16:21:45 +00:00
$deployment = $this -> client -> call ( Client :: METHOD_POST , '/functions/' . $functionId . '/deployments' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'multipart/form-data' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
2024-09-20 08:33:26 +00:00
'code' => $this -> packageFunction ( 'timeout' ),
2023-03-01 12:00:36 +00:00
'activate' => true
2021-12-06 12:03:12 +00:00
]);
2022-01-24 23:46:43 +00:00
$deploymentId = $deployment [ 'body' ][ '$id' ] ? ? '' ;
2021-12-06 12:03:12 +00:00
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 202 , $deployment [ 'headers' ][ 'status-code' ]);
2022-01-24 23:46:43 +00:00
$this -> assertNotEmpty ( $deployment [ 'body' ][ '$id' ]);
2021-12-06 12:03:12 +00:00
2023-08-29 03:15:02 +00:00
// Poll until deployment is built
2025-03-03 16:07:33 +00:00
$this -> assertEventually ( function () use ( $function , $deploymentId ) {
2023-08-29 03:15:02 +00:00
$deployment = $this -> client -> call ( Client :: METHOD_GET , '/functions/' . $function [ 'body' ][ '$id' ] . '/deployments/' . $deploymentId , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]);
2025-03-03 16:07:33 +00:00
$this -> assertEquals ( 'ready' , $deployment [ 'body' ][ 'status' ], \json_encode ( $deployment [ 'body' ]));
2026-03-16 12:30:55 +00:00
}, 240_000 , 500 );
2022-01-06 09:45:56 +00:00
2022-04-18 16:21:45 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/functions/' . $functionId . '/deployments/' . $deploymentId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
2022-02-15 09:16:32 +00:00
]), []);
2021-12-06 12:03:12 +00:00
2024-05-06 07:55:11 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2022-04-18 16:21:45 +00:00
$execution = $this -> client -> call ( Client :: METHOD_POST , '/functions/' . $functionId . '/executions' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ]
2022-09-07 08:24:49 +00:00
], $this -> getHeaders ()), [
'async' => true
]);
2021-12-06 12:03:12 +00:00
2024-05-06 07:55:11 +00:00
$this -> assertEquals ( 202 , $execution [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $execution [ 'body' ][ '$id' ]);
$response = json_decode ( $client -> receive (), true );
$responseUpdate = json_decode ( $client -> receive (), true );
2022-04-18 16:21:45 +00:00
$executionId = $execution [ 'body' ][ '$id' ];
2021-12-06 12:03:12 +00:00
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 8 , $response [ 'data' ][ 'channels' ]);
2022-02-28 15:17:37 +00:00
$this -> assertContains ( 'console' , $response [ 'data' ][ 'channels' ]);
2024-09-30 14:53:25 +00:00
$this -> assertContains ( " projects. { $this -> getProject ()[ '$id' ] } " , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'executions' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " executions. { $executionId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " functions. { $functionId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " functions. { $functionId } .executions. { $executionId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions. { $executionId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions. { $executionId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions. { $executionId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'type' , $responseUpdate );
$this -> assertArrayHasKey ( 'data' , $responseUpdate );
$this -> assertEquals ( 'event' , $responseUpdate [ 'type' ]);
$this -> assertNotEmpty ( $responseUpdate [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $responseUpdate [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 8 , $responseUpdate [ 'data' ][ 'channels' ]);
2022-02-28 15:42:52 +00:00
$this -> assertContains ( 'console' , $responseUpdate [ 'data' ][ 'channels' ]);
2024-09-30 14:53:25 +00:00
$this -> assertContains ( " projects. { $this -> getProject ()[ '$id' ] } " , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'executions' , $responseUpdate [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " executions. { $executionId } " , $responseUpdate [ 'data' ][ 'channels' ]);
$this -> assertContains ( " functions. { $functionId } " , $responseUpdate [ 'data' ][ 'channels' ]);
$this -> assertContains ( " functions. { $functionId } .executions. { $executionId } .update " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions. { $executionId } " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions.*.update " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } .executions.* " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions. { $functionId } " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions. { $executionId } .update " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions. { $executionId } " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions.*.update " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.*.executions.* " , $responseUpdate [ 'data' ][ 'events' ]);
$this -> assertContains ( " functions.* " , $responseUpdate [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $responseUpdate [ 'data' ][ 'payload' ]);
$client -> close ();
2022-02-09 20:20:28 +00:00
2022-05-23 14:54:50 +00:00
// Cleanup : Delete function
2022-04-18 16:21:45 +00:00
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/functions/' . $functionId , [
2022-02-09 20:20:28 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], []);
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
}
2026-02-25 10:38:40 +00:00
public function testChannelTeams () : void
2021-12-06 12:03:12 +00:00
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'teams' ], [
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'teams' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test Team Create
*/
$team = $this -> client -> call ( Client :: METHOD_POST , '/teams' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
2022-08-14 10:33:36 +00:00
'teamId' => ID :: unique (),
2021-12-06 12:03:12 +00:00
'name' => 'Arsenal'
]);
$teamId = $team [ 'body' ][ '$id' ] ? ? '' ;
$this -> assertEquals ( 201 , $team [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $team [ 'body' ][ '$id' ]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'teams' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'teams.create' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " teams. { $teamId } .create " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " teams. { $teamId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
/**
* Test Team Update
*/
2022-04-18 16:21:45 +00:00
$team = $this -> client -> call ( Client :: METHOD_PUT , '/teams/' . $teamId , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'name' => 'Manchester'
]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 200 , $team [ 'headers' ][ 'status-code' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $team [ 'body' ][ '$id' ]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'teams' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'teams.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " teams. { $teamId } .update " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " teams. { $teamId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2023-03-06 14:24:02 +00:00
/**
* Test Team Update Prefs
*/
2023-03-23 12:06:48 +00:00
$team = $this -> client -> call ( Client :: METHOD_PUT , '/teams/' . $teamId . '/prefs' , array_merge ([
2023-03-06 14:24:02 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'prefs' => [
'funcKey1' => 'funcValue1' ,
'funcKey2' => 'funcValue2' ,
]
]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 200 , $team [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'funcValue1' , $team [ 'body' ][ 'funcKey1' ]);
$this -> assertEquals ( 'funcValue2' , $team [ 'body' ][ 'funcKey2' ]);
2023-03-06 14:24:02 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2023-03-06 14:24:02 +00:00
$this -> assertContains ( 'teams' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'teams.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " teams. { $teamId } .update " , $response [ 'data' ][ 'channels' ]);
2023-03-06 14:24:02 +00:00
$this -> assertContains ( " teams. { $teamId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } .update.prefs " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.update.prefs " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
2025-05-09 09:41:14 +00:00
$this -> assertEquals ( 'funcValue1' , $response [ 'data' ][ 'payload' ][ 'funcKey1' ]);
$this -> assertEquals ( 'funcValue2' , $response [ 'data' ][ 'payload' ][ 'funcKey2' ]);
2023-03-06 14:24:02 +00:00
2021-12-06 12:03:12 +00:00
$client -> close ();
}
2026-02-25 10:38:40 +00:00
public function testChannelMemberships () : void
2021-12-06 12:03:12 +00:00
{
2026-02-25 10:38:40 +00:00
$data = $this -> createTeam ();
$teamId = $data [ 'teamId' ];
2021-12-06 12:03:12 +00:00
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'memberships' ], [
'origin' => 'http://localhost' ,
2022-04-18 16:21:45 +00:00
'cookie' => 'a_session_' . $projectId . '=' . $session
2021-12-06 12:03:12 +00:00
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 1 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'memberships' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
2022-04-18 16:21:45 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/teams/' . $teamId . '/memberships' , array_merge ([
2021-12-06 12:03:12 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$membershipId = $response [ 'body' ][ 'memberships' ][ 0 ][ '$id' ];
/**
* Test Update Membership
*/
$roles = [ 'admin' , 'editor' , 'uncle' ];
2022-04-18 16:21:45 +00:00
$this -> client -> call ( Client :: METHOD_PATCH , '/teams/' . $teamId . '/memberships/' . $membershipId , array_merge ([
2021-12-06 12:03:12 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'roles' => $roles
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 4 , $response [ 'data' ][ 'channels' ]);
2021-12-06 12:03:12 +00:00
$this -> assertContains ( 'memberships' , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " memberships. { $membershipId } " , $response [ 'data' ][ 'channels' ]);
2026-04-27 11:10:15 +00:00
$this -> assertContains ( 'memberships.update' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " memberships. { $membershipId } .update " , $response [ 'data' ][ 'channels' ]);
2022-04-18 16:21:45 +00:00
$this -> assertContains ( " teams. { $teamId } .memberships. { $membershipId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } .memberships. { $membershipId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } .memberships.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } .memberships.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams. { $teamId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.memberships. { $membershipId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.memberships. { $membershipId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.memberships.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.*.memberships.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " teams.* " , $response [ 'data' ][ 'events' ]);
2021-12-06 12:03:12 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$client -> close ();
}
2025-10-02 12:57:20 +00:00
2026-02-25 13:28:33 +00:00
public function testChannelsTablesDB ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
2026-03-02 08:42:13 +00:00
/**
* Create a shared TablesDB database using the / tablesdb API .
* This database will then be accessed via both / databases and / tablesdb routes .
*/
2026-02-25 13:28:33 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
2026-03-02 08:42:13 +00:00
]), [
2026-02-25 13:28:33 +00:00
'databaseId' => ID :: unique (),
2026-03-02 08:42:13 +00:00
'name' => 'TablesDB Cross API Realtime DB' ,
2026-02-25 13:28:33 +00:00
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-03-02 08:42:13 +00:00
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
/**
* Legacy collection in the shared database ( / databases API ) .
*/
$collection = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'collectionId' => ID :: unique (),
'name' => 'Legacy Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $user [ '$id' ])),
],
'documentSecurity' => true ,
]);
$collectionId = $collection [ 'body' ][ '$id' ];
$attribute = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $attribute [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $databaseId , $collectionId ) {
$attribute = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $attribute [ 'body' ][ 'status' ]);
}, 30000 , 250 );
/**
* TablesDB table in the same database ( / tablesdb API ) .
*/
2026-02-25 13:28:33 +00:00
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'tableId' => ID :: unique (),
'name' => 'Actors' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$tableId = $table [ 'body' ][ '$id' ];
$column = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $databaseId , $tableId ) {
$column = $this -> client -> call ( Client :: METHOD_GET , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'available' , $column [ 'body' ][ 'status' ]);
}, 120000 , 500 );
2026-03-02 08:42:13 +00:00
/**
* Two different clients subscribing via legacy ( documents / collections )
* and new ( rows / tables ) channels .
*/
$clientLegacy = $this -> getWebsocket ([ 'documents' , 'collections' ], [
2026-02-25 13:28:33 +00:00
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
2026-03-02 08:42:13 +00:00
$response = json_decode ( $clientLegacy -> receive (), true );
2026-02-25 13:28:33 +00:00
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
2026-03-02 08:42:13 +00:00
$clientTables = $this -> getWebsocket ([ 'rows' , 'tables' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
$response = json_decode ( $clientTables -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertContains ( 'rows' , $response [ 'data' ][ 'channels' ]);
/**
* 1 ) Operation via legacy / databases API ( document create ) .
* Both clients should receive an event that includes both document -
* style and row - style channels on the shared database .
*/
$documentId = ID :: unique ();
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'documentId' => $documentId ,
'data' => [
'name' => 'Legacy Chris Evans' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $document [ 'headers' ][ 'status-code' ]);
$legacyEventForLegacyClient = json_decode ( $clientLegacy -> receive (), true );
$legacyEventForTablesClient = json_decode ( $clientTables -> receive (), true );
foreach ([ $legacyEventForLegacyClient , $legacyEventForTablesClient ] as $event ) {
$this -> assertArrayHasKey ( 'type' , $event );
$this -> assertArrayHasKey ( 'data' , $event );
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertNotEmpty ( $event [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $event [ 'data' ]);
$channels = $event [ 'data' ][ 'channels' ];
// Legacy-style channels
$this -> assertContains ( 'documents' , $channels );
$this -> assertContains ( " databases. { $databaseId } .collections. { $collectionId } .documents " , $channels );
$this -> assertContains ( " databases. { $databaseId } .collections. { $collectionId } .documents. { $documentId } " , $channels );
// New rows-style channels mirrored for legacy API
$this -> assertContains ( 'rows' , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $collectionId } .rows " , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $collectionId } .rows. { $documentId } " , $channels );
// TablesDB-prefixed channels should also be present for a tablesdb database
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $collectionId } .rows " , $channels );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $collectionId } .rows. { $documentId } " , $channels );
}
/**
* 2 ) Operation via / tablesdb API ( row create ) .
* Both clients should again receive an event that now also includes
* the tablesdb - prefixed channels alongside the databases - prefixed ones .
*/
2026-02-25 13:28:33 +00:00
$rowId = ID :: unique ();
$row = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'rowId' => $rowId ,
'data' => [
'name' => 'Chris Evans' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
2026-03-02 08:42:13 +00:00
$tablesEventForLegacyClient = json_decode ( $clientLegacy -> receive (), true );
$tablesEventForTablesClient = json_decode ( $clientTables -> receive (), true );
foreach ([ $tablesEventForLegacyClient , $tablesEventForTablesClient ] as $event ) {
$this -> assertArrayHasKey ( 'type' , $event );
$this -> assertArrayHasKey ( 'data' , $event );
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertNotEmpty ( $event [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $event [ 'data' ]);
$channels = $event [ 'data' ][ 'channels' ];
// Core tablesdb row channels
$this -> assertContains ( 'rows' , $channels );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $tableId } .rows " , $channels );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $tableId } .rows. { $rowId } " , $channels );
// Collections/legacy-style compatibility channels
$this -> assertContains ( 'documents' , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows " , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows. { $rowId } " , $channels );
$this -> assertContains ( " databases. { $databaseId } .collections. { $tableId } .documents " , $channels );
$this -> assertContains ( " databases. { $databaseId } .collections. { $tableId } .documents. { $rowId } " , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows. { $rowId } .create " , $event [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $event [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Chris Evans' , $event [ 'data' ][ 'payload' ][ 'name' ]);
}
/**
* 3 ) Legacy database accessed via / tablesdb routes .
* A database created via / databases but operated on via / tablesdb
* should also expose both legacy and tablesdb - prefixed channels .
*/
$legacyDatabase = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'databaseId' => ID :: unique (),
'name' => 'Legacy DB via TablesDB Route' ,
]);
$this -> assertEquals ( 201 , $legacyDatabase [ 'headers' ][ 'status-code' ]);
$legacyDatabaseId = $legacyDatabase [ 'body' ][ '$id' ];
$legacyTable = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $legacyDatabaseId . '/tables' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'tableId' => ID :: unique (),
'name' => 'Legacy Actors' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$legacyTableId = $legacyTable [ 'body' ][ '$id' ];
$legacyColumn = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $legacyDatabaseId . '/tables/' . $legacyTableId . '/columns/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $legacyColumn [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $legacyDatabaseId , $legacyTableId ) {
$column = $this -> client -> call ( Client :: METHOD_GET , '/tablesdb/' . $legacyDatabaseId . '/tables/' . $legacyTableId . '/columns/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'available' , $column [ 'body' ][ 'status' ]);
}, 120000 , 500 );
$legacyRowId = ID :: unique ();
$legacyRow = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $legacyDatabaseId . '/tables/' . $legacyTableId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'rowId' => $legacyRowId ,
'data' => [
'name' => 'Legacy Tables Route' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $legacyRow [ 'headers' ][ 'status-code' ]);
$legacyTablesEventForLegacyClient = json_decode ( $clientLegacy -> receive (), true );
$legacyTablesEventForTablesClient = json_decode ( $clientTables -> receive (), true );
foreach ([ $legacyTablesEventForLegacyClient , $legacyTablesEventForTablesClient ] as $event ) {
$this -> assertArrayHasKey ( 'type' , $event );
$this -> assertArrayHasKey ( 'data' , $event );
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertNotEmpty ( $event [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $event [ 'data' ]);
$channels = $event [ 'data' ][ 'channels' ];
$events = $event [ 'data' ][ 'events' ];
$this -> assertIsList ( $channels );
$this -> assertIsList ( $events );
// Core tablesdb row channels for legacy db accessed via tablesdb
$this -> assertContains ( 'rows' , $channels );
$this -> assertContains ( " tablesdb. { $legacyDatabaseId } .tables. { $legacyTableId } .rows " , $channels );
$this -> assertContains ( " tablesdb. { $legacyDatabaseId } .tables. { $legacyTableId } .rows. { $legacyRowId } " , $channels );
// Legacy compatibility channels must also exist
$this -> assertContains ( 'documents' , $channels );
$this -> assertContains ( " databases. { $legacyDatabaseId } .tables. { $legacyTableId } .rows " , $channels );
$this -> assertContains ( " databases. { $legacyDatabaseId } .tables. { $legacyTableId } .rows. { $legacyRowId } " , $channels );
$this -> assertContains ( " databases. { $legacyDatabaseId } .collections. { $legacyTableId } .documents " , $channels );
$this -> assertContains ( " databases. { $legacyDatabaseId } .collections. { $legacyTableId } .documents. { $legacyRowId } " , $channels );
}
$clientLegacy -> close ();
$clientTables -> close ();
}
public function testChannelTablesDBRowUpdate ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
/**
* Create a tablesdb database + table + column + row .
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'databaseId' => ID :: unique (),
'name' => 'Row Update DB' ,
]);
$this -> assertEquals ( 201 , $database [ 'headers' ][ 'status-code' ]);
$databaseId = $database [ 'body' ][ '$id' ];
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'tableId' => ID :: unique (),
'name' => 'Assembly' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $table [ 'headers' ][ 'status-code' ]);
$tableId = $table [ 'body' ][ '$id' ];
$column = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $databaseId , $tableId ) {
$column = $this -> client -> call ( Client :: METHOD_GET , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/columns/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'available' , $column [ 'body' ][ 'status' ]);
}, 120000 , 500 );
// Seed a row so we can listen to its update
$rowId = ID :: unique ();
$row = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'rowId' => $rowId ,
'data' => [
'name' => 'Initial Name' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
/**
* Subscribe to a specific row channel using both legacy and tablesdb - style prefixes .
* This mimics a client subscribing to a concrete " resource " channel and
* expecting a single event list for updates .
*/
$client = $this -> getWebsocket ([
" databases. { $databaseId } .tables. { $tableId } .rows. { $rowId } " ,
" tablesdb. { $databaseId } .tables. { $tableId } .rows. { $rowId } " ,
], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
2026-02-25 13:28:33 +00:00
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
2026-03-02 08:42:13 +00:00
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
2026-02-25 13:28:33 +00:00
$this -> assertNotEmpty ( $response [ 'data' ]);
2026-03-02 08:42:13 +00:00
$this -> assertIsList ( $response [ 'data' ][ 'channels' ]);
2026-02-25 13:28:33 +00:00
2026-03-02 08:42:13 +00:00
/**
* Trigger a row update via the dedicated / tablesdb row update endpoint .
* Event label : databases . [ databaseId ] . tables . [ tableId ] . rows . [ rowId ] . update
* Our Event + Realtime logic should enrich this to include :
* - databases . { dbId } . tables . { tableId } . rows . { rowId } . update
* - tablesdb . { dbId } . tables . { tableId } . rows . { rowId } . update
*/
$update = $this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/' . $databaseId . '/tables/' . $tableId . '/rows/' . $rowId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'data' => [
'name' => 'Updated Name' ,
],
]);
2026-02-25 13:28:33 +00:00
2026-03-02 08:42:13 +00:00
$this -> assertEquals ( 200 , $update [ 'headers' ][ 'status-code' ]);
2026-02-25 13:28:33 +00:00
2026-03-31 18:46:02 +00:00
// Drain WebSocket messages until the .update event arrives.
// Earlier events (e.g. a late-arriving .create from the row seed above) are skipped.
$updateEvent = " tablesdb. { $databaseId } .tables. { $tableId } .rows. { $rowId } .update " ;
$event = null ;
$deadline = \time () + 10 ;
while ( \time () < $deadline ) {
2026-04-01 17:01:00 +00:00
$raw = $client -> receive ();
2026-03-31 18:46:02 +00:00
$msg = json_decode ( $raw , true );
if (( $msg [ 'type' ] ? ? '' ) === 'event' && \in_array ( $updateEvent , $msg [ 'data' ][ 'events' ] ? ? [])) {
$event = $msg ;
break ;
}
}
2026-02-25 13:28:33 +00:00
2026-03-31 18:46:02 +00:00
$this -> assertNotNull ( $event , 'Timed out waiting for the row update event' );
2026-03-02 08:42:13 +00:00
$this -> assertArrayHasKey ( 'type' , $event );
$this -> assertArrayHasKey ( 'data' , $event );
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertNotEmpty ( $event [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $event [ 'data' ]);
$channels = $event [ 'data' ][ 'channels' ];
$events = $event [ 'data' ][ 'events' ];
// Ensure channels and events are list-type arrays
$this -> assertIsList ( $channels );
$this -> assertIsList ( $events );
// Legacy + tablesdb row channels must be present
$this -> assertContains ( 'rows' , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows " , $channels );
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows. { $rowId } " , $channels );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $tableId } .rows " , $channels );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $tableId } .rows. { $rowId } " , $channels );
// Both databases.* and tablesdb.* update events should be emitted
$this -> assertContains ( " databases. { $databaseId } .tables. { $tableId } .rows. { $rowId } .update " , $events );
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $tableId } .rows. { $rowId } .update " , $events );
$this -> assertNotEmpty ( $event [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Updated Name' , $event [ 'data' ][ 'payload' ][ 'name' ]);
2026-02-25 13:28:33 +00:00
$client -> close ();
}
2025-10-10 05:25:48 +00:00
public function testChannelDatabaseTransaction ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
/**
* Setup Database and Collection
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Transactions DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
$collection = $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 Collection' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$collectionId = $collection [ 'body' ][ '$id' ];
$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' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $collectionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-10-10 05:25:48 +00:00
/**
* Test Transaction Create with Single Document
*/
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'ttl' => 3600 // 1 hour
]);
$this -> assertEquals ( 201 , $transaction [ 'headers' ][ 'status-code' ], 'Failed to create transaction: ' . json_encode ( $transaction [ 'body' ]));
$this -> assertNotEmpty ( $transaction [ 'body' ][ '$id' ]);
$transactionId = $transaction [ 'body' ][ '$id' ];
$documentId = ID :: unique ();
$operationsResponse = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions/' . $transactionId . '/operations' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId ,
'action' => 'create' ,
'data' => [
'name' => 'Transaction Document' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
]
]
]);
$this -> assertEquals ( 201 , $operationsResponse [ 'headers' ][ 'status-code' ], 'Failed to add operations: ' . json_encode ( $operationsResponse [ 'body' ]));
// Commit transaction
$commitResponse = $this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/transactions/' . $transactionId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$this -> assertEquals ( 200 , $commitResponse [ 'headers' ][ 'status-code' ], 'Failed to commit transaction: ' . json_encode ( $commitResponse [ 'body' ]));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $collectionId } .rows. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Transaction Document' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Transaction Update
*/
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'ttl' => 3600
]);
$transactionId = $transaction [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions/' . $transactionId . '/operations' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId ,
'action' => 'update' ,
'data' => [
'name' => 'Updated Transaction Document' ,
],
]
]
]);
$this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/transactions/' . $transactionId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $collectionId } .rows. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertEquals ( 'Updated Transaction Document' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Transaction Delete
*/
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'ttl' => 3600
]);
$transactionId = $transaction [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions/' . $transactionId . '/operations' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId ,
'action' => 'delete' ,
]
]
]);
$this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/transactions/' . $transactionId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $collectionId } .rows. { $documentId } .delete " , $response [ 'data' ][ 'events' ]);
$client -> close ();
}
2026-04-15 11:53:18 +00:00
public function testChannelMirrorEventsAcrossDatabasesAndTablesdb () : void
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
/**
* Case 1 : Trigger event through / databases route and verify both
* legacy collections / documents and tables / rows events are generated .
*/
$legacyDatabase = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'databaseId' => ID :: unique (),
'name' => 'Mirror Legacy DB' ,
]);
$this -> assertEquals ( 201 , $legacyDatabase [ 'headers' ][ 'status-code' ]);
$legacyDatabaseId = $legacyDatabase [ 'body' ][ '$id' ];
$legacyCollection = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $legacyDatabaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'collectionId' => ID :: unique (),
'name' => 'Legacy Collection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'documentSecurity' => true ,
]);
$legacyCollectionId = $legacyCollection [ 'body' ][ '$id' ];
$attribute = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $legacyDatabaseId . '/collections/' . $legacyCollectionId . '/attributes/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $attribute [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $legacyDatabaseId , $legacyCollectionId ) {
$attribute = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $legacyDatabaseId . '/collections/' . $legacyCollectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $attribute [ 'body' ][ 'status' ]);
}, 30000 , 250 );
$legacyClient = $this -> getWebsocket ([
" databases. { $legacyDatabaseId } .collections. { $legacyCollectionId } .documents " ,
" databases. { $legacyDatabaseId } .tables. { $legacyCollectionId } .rows " ,
], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
$connected = json_decode ( $legacyClient -> receive (), true );
$this -> assertEquals ( 'connected' , $connected [ 'type' ]);
$legacyDocumentId = ID :: unique ();
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $legacyDatabaseId . '/collections/' . $legacyCollectionId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'documentId' => $legacyDocumentId ,
'data' => [
'name' => 'legacy-route-create' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $document [ 'headers' ][ 'status-code' ]);
$legacyEvent = json_decode ( $legacyClient -> receive (), true );
$this -> assertEquals ( 'event' , $legacyEvent [ 'type' ]);
$this -> assertContains (
" databases. { $legacyDatabaseId } .collections. { $legacyCollectionId } .documents. { $legacyDocumentId } .create " ,
$legacyEvent [ 'data' ][ 'events' ]
);
$this -> assertContains (
" databases. { $legacyDatabaseId } .tables. { $legacyCollectionId } .rows. { $legacyDocumentId } .create " ,
$legacyEvent [ 'data' ][ 'events' ]
);
$legacyClient -> close ();
/**
* Case 2 : Trigger event through / tablesdb route and verify both
* tables / rows and legacy collections / documents events are generated .
*/
$tablesDatabase = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'databaseId' => ID :: unique (),
'name' => 'Mirror TablesDB' ,
]);
$this -> assertEquals ( 201 , $tablesDatabase [ 'headers' ][ 'status-code' ]);
$tablesDatabaseId = $tablesDatabase [ 'body' ][ '$id' ];
$table = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $tablesDatabaseId . '/tables' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'tableId' => ID :: unique (),
'name' => 'Mirror Table' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $table [ 'headers' ][ 'status-code' ]);
$tableId = $table [ 'body' ][ '$id' ];
$column = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $tablesDatabaseId . '/tables/' . $tableId . '/columns/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEventually ( function () use ( $tablesDatabaseId , $tableId ) {
$column = $this -> client -> call ( Client :: METHOD_GET , '/tablesdb/' . $tablesDatabaseId . '/tables/' . $tableId . '/columns/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 'available' , $column [ 'body' ][ 'status' ]);
}, 120000 , 500 );
$tablesClient = $this -> getWebsocket ([
" databases. { $tablesDatabaseId } .tables. { $tableId } .rows " ,
" databases. { $tablesDatabaseId } .collections. { $tableId } .documents " ,
], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
$connected = json_decode ( $tablesClient -> receive (), true );
$this -> assertEquals ( 'connected' , $connected [ 'type' ]);
$rowId = ID :: unique ();
$row = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $tablesDatabaseId . '/tables/' . $tableId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'rowId' => $rowId ,
'data' => [
'name' => 'tablesdb-route-create' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $row [ 'headers' ][ 'status-code' ]);
$tablesEvent = json_decode ( $tablesClient -> receive (), true );
$this -> assertEquals ( 'event' , $tablesEvent [ 'type' ]);
$this -> assertContains (
" databases. { $tablesDatabaseId } .tables. { $tableId } .rows. { $rowId } .create " ,
$tablesEvent [ 'data' ][ 'events' ]
);
$this -> assertContains (
" databases. { $tablesDatabaseId } .collections. { $tableId } .documents. { $rowId } .create " ,
$tablesEvent [ 'data' ][ 'events' ]
);
$tablesClient -> close ();
2026-04-15 12:08:21 +00:00
/**
* Case 3 : Trigger event through / documentsdb route and verify only
* documentsdb events are generated ( no databases / tablesdb mirrors ) .
*/
$documentsDatabase = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'databaseId' => ID :: unique (),
'name' => 'Mirror DocumentsDB' ,
]);
$this -> assertEquals ( 201 , $documentsDatabase [ 'headers' ][ 'status-code' ]);
$documentsDatabaseId = $documentsDatabase [ 'body' ][ '$id' ];
$documentsCollection = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb/' . $documentsDatabaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), [
'collectionId' => ID :: unique (),
'name' => 'Mirror Documents Collection' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'documentSecurity' => true ,
]);
$this -> assertEquals ( 201 , $documentsCollection [ 'headers' ][ 'status-code' ]);
$documentsCollectionId = $documentsCollection [ 'body' ][ '$id' ];
$documentsClient = $this -> getWebsocket ([
" documentsdb. { $documentsDatabaseId } .collections. { $documentsCollectionId } .documents " ,
'documents' ,
], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]);
$connected = json_decode ( $documentsClient -> receive (), true );
$this -> assertEquals ( 'connected' , $connected [ 'type' ]);
$documentsDocumentId = ID :: unique ();
$documentsDocument = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb/' . $documentsDatabaseId . '/collections/' . $documentsCollectionId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()), [
'documentId' => $documentsDocumentId ,
'data' => [
'name' => 'documentsdb-route-create' ,
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$this -> assertEquals ( 201 , $documentsDocument [ 'headers' ][ 'status-code' ]);
$documentsEvent = json_decode ( $documentsClient -> receive (), true );
$this -> assertEquals ( 'event' , $documentsEvent [ 'type' ]);
$this -> assertContains (
" documentsdb. { $documentsDatabaseId } .collections. { $documentsCollectionId } .documents. { $documentsDocumentId } .create " ,
$documentsEvent [ 'data' ][ 'events' ]
);
$this -> assertEmpty (
array_filter (
$documentsEvent [ 'data' ][ 'events' ],
fn ( string $event ) => \str_starts_with ( $event , 'databases.' ) || \str_starts_with ( $event , 'tablesdb.' )
)
);
$documentsClient -> close ();
2026-04-15 11:53:18 +00:00
}
2025-10-10 05:25:48 +00:00
public function testChannelDatabaseTransactionMultipleOperations ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
/**
* Setup Database and Collection
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Multi-Op DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
$collection = $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 Collection' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$collectionId = $collection [ 'body' ][ '$id' ];
$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' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $collectionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-10-10 05:25:48 +00:00
/**
* Test Multiple Operations in Single Transaction
*/
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'ttl' => 3600
]);
$transactionId = $transaction [ 'body' ][ '$id' ];
$documentId1 = ID :: unique ();
$documentId2 = ID :: unique ();
$documentId3 = ID :: unique ();
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions/' . $transactionId . '/operations' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId1 ,
'action' => 'create' ,
'data' => [
'name' => 'Doc 1' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
],
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId2 ,
'action' => 'create' ,
'data' => [
'name' => 'Doc 2' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
],
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId3 ,
'action' => 'create' ,
'data' => [
'name' => 'Doc 3' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
]
]
]);
$this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/transactions/' . $transactionId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'commit' => true
]);
// Should receive 3 events, one for each document
$response1 = json_decode ( $client -> receive (), true );
$response2 = json_decode ( $client -> receive (), true );
$response3 = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'event' , $response1 [ 'type' ]);
$this -> assertEquals ( 'event' , $response2 [ 'type' ]);
$this -> assertEquals ( 'event' , $response3 [ 'type' ]);
$receivedDocIds = [
$response1 [ 'data' ][ 'payload' ][ '$id' ],
$response2 [ 'data' ][ 'payload' ][ '$id' ],
$response3 [ 'data' ][ 'payload' ][ '$id' ],
];
$this -> assertContains ( $documentId1 , $receivedDocIds );
$this -> assertContains ( $documentId2 , $receivedDocIds );
$this -> assertContains ( $documentId3 , $receivedDocIds );
$client -> close ();
}
public function testChannelDatabaseTransactionRollback ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
/**
* Setup Database and Collection
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Rollback DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
$collection = $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 Collection' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$collectionId = $collection [ 'body' ][ '$id' ];
$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' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $collectionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-10-10 05:25:48 +00:00
/**
* Test Transaction Rollback - Should NOT trigger realtime events
*/
$transaction = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'ttl' => 3600
]);
$transactionId = $transaction [ 'body' ][ '$id' ];
$documentId = ID :: unique ();
$this -> client -> call ( Client :: METHOD_POST , '/tablesdb/transactions/' . $transactionId . '/operations' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'operations' => [
[
'databaseId' => $databaseId ,
'tableId' => $collectionId ,
'rowId' => $documentId ,
'action' => 'create' ,
'data' => [ 'name' => 'Rollback Document' ],
]
]
]);
// Rollback transaction
$this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/transactions/' . $transactionId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'rollback' => true
]);
// Wait a bit to ensure no event is received
sleep ( 1 );
try {
2025-12-24 13:26:55 +00:00
$client -> receive ();
2025-10-10 05:25:48 +00:00
$this -> fail ( 'Should not receive any event after rollback' );
} catch ( TimeoutException $e ) {
// Expected - no event should be triggered
chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
2026-04-19 12:01:20 +00:00
$this -> addToAssertionCount ( 1 );
2025-10-10 05:25:48 +00:00
}
$client -> close ();
}
2025-10-02 12:57:20 +00:00
public function testRelationshipPayloadHidesRelatedDoc ()
{
2026-02-25 10:38:40 +00:00
if ( ! $this -> getSupportForRelationships ()) {
$this -> expectNotToPerformAssertions ();
return ;
}
2025-10-02 12:57:20 +00:00
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
// Create database
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'db-rel'
]);
$databaseId = $database [ 'body' ][ '$id' ];
$level1 = $this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'collectionId' => ID :: unique (),
'name' => 'level1' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'documentSecurity' => true ,
]);
$level1Id = $level1 [ 'body' ][ '$id' ];
$level2 = $this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'collectionId' => ID :: unique (),
'name' => 'level2' ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: create ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'documentSecurity' => true ,
]);
$level2Id = $level2 [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $level1Id } /attributes/string " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => false ,
]);
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $level2Id } /attributes/string " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => false ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $level1Id ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $level1Id . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
$this -> assertEventually ( function () use ( $databaseId , $level2Id ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $level2Id . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-10-02 12:57:20 +00:00
// two-way one-to-one relationship from level1 to level2
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $level1Id } /attributes/relationship " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'relatedCollectionId' => $level2Id ,
'type' => 'oneToOne' ,
'twoWay' => true ,
'key' => 'level2Ref' ,
'onDelete' => 'cascade' ,
]);
2026-02-25 10:38:40 +00:00
$this -> assertEventually ( function () use ( $databaseId , $level1Id ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $level1Id . '/attributes/level2Ref' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2025-10-02 12:57:20 +00:00
$doc2 = $this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $level2Id } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
2025-10-10 05:25:48 +00:00
'data' => [ 'name' => 'L2' ],
2025-10-02 12:57:20 +00:00
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$doc2Id = $doc2 [ 'body' ][ '$id' ];
$doc1 = $this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $level1Id } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
2025-10-10 05:25:48 +00:00
'data' => [ 'name' => 'L1' ],
2025-10-02 12:57:20 +00:00
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$doc1Id = $doc1 [ 'body' ][ '$id' ];
json_decode ( $client -> receive (), true );
$this -> client -> call ( Client :: METHOD_PATCH , " /databases/ { $databaseId } /collections/ { $level1Id } /documents/ { $doc1Id } " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'level2Ref' => $doc2Id ,
],
]);
// payload should not contain the relationship attribute 'level2Ref'
$event = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $event );
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertArrayHasKey ( 'data' , $event );
$this -> assertNotEmpty ( $event [ 'data' ]);
$this -> assertArrayHasKey ( 'payload' , $event [ 'data' ]);
$this -> assertArrayHasKey ( '$id' , $event [ 'data' ][ 'payload' ]);
$this -> assertEquals ( $doc1Id , $event [ 'data' ][ 'payload' ][ '$id' ]);
$this -> assertArrayNotHasKey ( 'level2Ref' , $event [ 'data' ][ 'payload' ]);
2025-10-08 19:05:38 +00:00
$client -> close ();
}
2025-12-24 08:11:31 +00:00
/**
* Simulate concurrent realtime traffic using Swoole coroutines .
* Opens multiple websocket clients concurrently , then performs create / update / delete ops .
*/
public function testConcurrentRealtimeTrafficCoroutines ()
{
if ( ! class_exists ( \Swoole\Coroutine :: class )) {
$this -> markTestSkipped ( 'Swoole Coroutine not available in this environment.' );
}
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
2026-03-13 23:55:14 +00:00
// Setup DB/collection/attribute outside coroutine to avoid fatal errors on assertion failure
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Concurrent DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
$collection = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'collectionId' => ID :: unique (),
'name' => 'Concurrent Collection' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$collectionId = $collection [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $collectionId } /attributes/string " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 64 ,
'required' => true ,
]);
$this -> assertEventually ( function () use ( $databaseId , $collectionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ] ? ? null );
2026-03-14 08:00:36 +00:00
}, 30000 , 250 );
2026-03-13 23:55:14 +00:00
Coroutine\run ( function () use ( $session , $projectId , $databaseId , $collectionId ) {
2025-12-24 08:11:31 +00:00
$headers = [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
];
$clientCount = 5 ;
$clients = [];
for ( $i = 0 ; $i < $clientCount ; $i ++ ) {
$clients [] = $this -> getWebsocket ([ 'documents' , 'collections' ], $headers );
}
foreach ( $clients as $client ) {
$response = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
}
$creates = [
[ 'name' => 'Doc A' ],
[ 'name' => 'Doc B' ],
[ 'name' => 'Doc C' ],
[ 'name' => 'Doc D' ],
[ 'name' => 'Doc E' ],
[ 'name' => 'Doc F' ],
];
$expectedEvents = count ( $creates );
// Per-client receipts
$receivedEvents = array_fill ( 0 , $clientCount , []);
// Launch receiver coroutines (one per client)
foreach ( $clients as $idx => $client ) {
Coroutine :: create ( function () use ( $client , & $receivedEvents , $expectedEvents , $idx ) {
$local = [];
for ( $i = 0 ; $i < $expectedEvents ; $i ++ ) {
$event = json_decode ( $client -> receive (), true );
$local [] = $event ;
}
$receivedEvents [ $idx ] = $local ;
});
}
// Create docs
foreach ( $creates as $payload ) {
$this -> client -> call ( Client :: METHOD_POST , " /databases/ { $databaseId } /collections/ { $collectionId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documentId' => ID :: unique (),
'data' => $payload ,
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
}
// Wait for receivers to collect; timeout ~10s
$deadline = microtime ( true ) + 10 ;
while ( microtime ( true ) < $deadline ) {
$done = true ;
foreach ( $receivedEvents as $events ) {
if ( count ( $events ) < $expectedEvents ) {
$done = false ;
break ;
}
}
if ( $done ) {
break ;
}
Coroutine :: sleep ( 0.1 );
}
$expectedNames = array_column ( $creates , 'name' );
for ( $c = 0 ; $c < $clientCount ; $c ++ ) {
$events = $receivedEvents [ $c ];
$this -> assertCount ( $expectedEvents , $events , 'Unexpected event count on client ' . $c );
$seen = [];
foreach ( $events as $event ) {
$this -> assertEquals ( 'event' , $event [ 'type' ]);
$this -> assertArrayHasKey ( 'payload' , $event [ 'data' ]);
$seen [] = $event [ 'data' ][ 'payload' ][ 'name' ] ? ? '' ;
}
foreach ( $expectedNames as $name ) {
$this -> assertContains ( $name , $seen );
}
}
foreach ( $clients as $client ) {
$client -> close ();
}
});
}
2026-03-19 15:00:42 +00:00
public function testChannelTablesDB ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test Database Create
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Actors DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
/**
* Test Collection Create
*/
$actors = $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' => 'Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$actorsId = $actors [ 'body' ][ '$id' ];
$name = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/columns/string' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'name' ,
'size' => 256 ,
'required' => true ,
]);
$this -> assertEquals ( 202 , $name [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'name' , $name [ 'body' ][ 'key' ]);
$this -> assertEquals ( 'string' , $name [ 'body' ][ 'type' ]);
$this -> assertEquals ( 256 , $name [ 'body' ][ 'size' ]);
$this -> assertTrue ( $name [ 'body' ][ 'required' ]);
2026-04-15 11:53:18 +00:00
$this -> assertEventually ( function () use ( $databaseId , $actorsId ) {
$column = $this -> client -> call ( Client :: METHOD_GET , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/columns/name' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], $this -> getHeaders ()));
$this -> assertEquals ( 200 , $column [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'available' , $column [ 'body' ][ 'status' ]);
}, 120000 , 500 );
2026-03-19 15:00:42 +00:00
/**
* Test Document Create
*/
$document = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'rowId' => ID :: unique (),
'data' => [
'name' => 'Chris Evans'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$rowId = $document [ 'body' ][ '$id' ];
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $rowId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.tables.' . $actorsId . '.rows.' . $rowId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'databases.' . $databaseId . '.tables.' . $actorsId . '.rows' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Chris Evans' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Document Update
*/
$document = $this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/' . $rowId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'rowId' => ID :: unique (),
'data' => [
'name' => 'Chris Evans 2'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows. { $rowId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Chris Evans 2' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Document Delete
*/
$document = $this -> client -> call ( Client :: METHOD_POST , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'rowId' => ID :: unique (),
'data' => [
'name' => 'Bradley Cooper'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$client -> receive ();
$rowId = $document [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_DELETE , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/' . $rowId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'rows' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows. { $rowId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .tables. { $actorsId } .rows " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows. { $rowId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Bradley Cooper' , $response [ 'data' ][ 'payload' ][ 'name' ]);
// test bulk create
$documents = $this -> client -> call ( Client :: METHOD_POST , " /tablesdb/ { $databaseId } /tables/ { $actorsId } /rows " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'rows' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
[
'$id' => ID :: unique (),
'name' => 'Scarlett Johansson' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
// Receive first document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.create " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// test bulk update
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/tablesdb/' . $databaseId . '/tables/' . $actorsId . '/rows/' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: update ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: delete ( Role :: user ( $this -> getUser ()[ '$id' ])),
]
],
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive second document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive third document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Test bulk delete
$response = $this -> client -> call ( Client :: METHOD_DELETE , " /tablesdb/ { $databaseId } /tables/ { $actorsId } /rows " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive third document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// bulk upsert
$this -> client -> call ( Client :: METHOD_PUT , " /tablesdb/ { $databaseId } /tables/ { $actorsId } /rows " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'rows' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " databases.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } .rows.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.*.tables. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb. { $databaseId } .tables.*.rows.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " tablesdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$client -> close ();
}
public function testChannelDocumentsdb ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
/**
* Test Database Create
*/
$database = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Actors DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
/**
* Test Collection Create
*/
$actors = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb/' . $databaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'collectionId' => ID :: unique (),
'name' => 'Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$actorsId = $actors [ 'body' ][ '$id' ];
/**
* Test Document Create
*/
$document = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
'data' => [
'name' => 'Chris Evans'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$documentId = $document [ 'body' ][ '$id' ];
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documentsdb.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
2026-04-15 12:03:57 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .create " , $response [ 'data' ][ 'events' ]);
$this -> assertEmpty ( array_filter ( $response [ 'data' ][ 'events' ], fn ( string $event ) => \str_starts_with ( $event , 'databases.' ) || \str_starts_with ( $event , 'tablesdb.' )));
2026-03-19 15:00:42 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Chris Evans' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Document Update
*/
$document = $this -> client -> call ( Client :: METHOD_PATCH , '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
'data' => [
'name' => 'Chris Evans 2'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
2026-04-15 12:03:57 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertEmpty ( array_filter ( $response [ 'data' ][ 'events' ], fn ( string $event ) => \str_starts_with ( $event , 'databases.' ) || \str_starts_with ( $event , 'tablesdb.' )));
2026-03-19 15:00:42 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Chris Evans 2' , $response [ 'data' ][ 'payload' ][ 'name' ]);
/**
* Test Document Delete
*/
$document = $this -> client -> call ( Client :: METHOD_POST , '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
'data' => [
'name' => 'Bradley Cooper'
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$client -> receive ();
$documentId = $document [ 'body' ][ '$id' ];
$this -> client -> call ( Client :: METHOD_DELETE , '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $documentId } " , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents " , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Bradley Cooper' , $response [ 'data' ][ 'payload' ][ 'name' ]);
// test bulk create
$documents = $this -> client -> call ( Client :: METHOD_POST , " /documentsdb/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
[
'$id' => ID :: unique (),
'name' => 'Scarlett Johansson' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
// Receive first document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: read ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: update ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$this -> assertContains ( Permission :: delete ( Role :: any ()), $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// test bulk update
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/documentsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'data' => [
'name' => 'Marvel Hero' ,
'$permissions' => [
Permission :: read ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: update ( Role :: user ( $this -> getUser ()[ '$id' ])),
Permission :: delete ( Role :: user ( $this -> getUser ()[ '$id' ])),
]
],
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive second document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Receive third document update event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.update " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 'Marvel Hero' , $response [ 'data' ][ 'payload' ][ 'name' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
// Test bulk delete
$response = $this -> client -> call ( Client :: METHOD_DELETE , " /documentsdb/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Receive first document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive second document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// Receive third document delete event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.delete " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
// bulk upsert
$this -> client -> call ( Client :: METHOD_PUT , " /documentsdb/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'$id' => ID :: unique (),
'name' => 'Robert Downey Jr.' ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents. { $response [ 'data' ][ 'payload' ][ '$id' ] } .upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } .documents.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.* " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.*.collections. { $actorsId } " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb. { $databaseId } .collections.*.documents.*.upsert " , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( " documentsdb.* " , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( 'name' , $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$permissions' , $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ '$permissions' ]);
$client -> close ();
}
public function testChannelVectorsDB ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertCount ( 2 , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'collections' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'user' ]);
$this -> assertEquals ( $user [ '$id' ], $response [ 'data' ][ 'user' ][ '$id' ]);
// Create VectorsDB database
$database = $this -> client -> call ( Client :: METHOD_POST , '/vectorsdb' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Actors VDB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
// Create collection in VectorsDB
$actors = $this -> client -> call ( Client :: METHOD_POST , '/vectorsdb/' . $databaseId . '/collections' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'collectionId' => ID :: unique (),
'name' => 'Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
'dimension' => 3 ,
]);
$actorsId = $actors [ 'body' ][ '$id' ];
// Create document in VectorsDB
$document = $this -> client -> call ( Client :: METHOD_POST , '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
'data' => [
'embeddings' => [ 1.0 , 0.0 , 0.0 ],
'metadata' => [ 'name' => 'Chris Evans' ]
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$documentId = $document [ 'body' ][ '$id' ];
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
// vectorsdb channels should include 3 items like documentsdb
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ 'embeddings' ]);
$this -> assertCount ( 3 , $response [ 'data' ][ 'payload' ][ 'embeddings' ]);
$this -> assertEquals ( 'Chris Evans' , $response [ 'data' ][ 'payload' ][ 'metadata' ][ 'name' ]);
// Update document
$this -> client -> call ( Client :: METHOD_PATCH , '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'data' => [
'embeddings' => [ 0.0 , 1.0 , 0.0 ],
'metadata' => [ 'name' => 'Chris Evans 2' ]
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ][ 'embeddings' ]);
$this -> assertEquals ( 'Chris Evans 2' , $response [ 'data' ][ 'payload' ][ 'metadata' ][ 'name' ]);
// Delete document
$this -> client -> call ( Client :: METHOD_DELETE , '/vectorsdb/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $documentId , $response [ 'data' ][ 'channels' ]);
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents' , $response [ 'data' ][ 'channels' ]);
// Bulk create two documents
$this -> client -> call ( Client :: METHOD_POST , " /vectorsdb/ { $databaseId } /collections/ { $actorsId } /documents " , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'documents' => [
[
'embeddings' => [ 1.0 , 0.0 , 0.0 ],
'metadata' => [ 'name' => 'Robert Downey Jr.' ],
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
],
[
'embeddings' => [ 0.0 , 1.0 , 0.0 ],
'metadata' => [ 'name' => 'Scarlett Johansson' ],
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]
],
]);
// Receive first bulk document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $response [ 'data' ][ 'payload' ][ '$id' ] . '.create' , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( 'vectorsdb.*.collections.*.documents.*.create' , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.*.documents.*.create' , $response [ 'data' ][ 'events' ]);
$this -> assertContains ( 'vectorsdb.*.collections.' . $actorsId . '.documents.*.create' , $response [ 'data' ][ 'events' ]);
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
// Receive second bulk document event
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 6 , $response [ 'data' ][ 'channels' ]);
2026-03-19 15:00:42 +00:00
$this -> assertContains ( 'vectorsdb.' . $databaseId . '.collections.' . $actorsId . '.documents.' . $response [ 'data' ][ 'payload' ][ '$id' ] . '.create' , $response [ 'data' ][ 'events' ]);
$client -> close ();
}
2026-04-03 16:30:03 +00:00
public function testChannelDatabaseAtomicOperations ()
{
$user = $this -> getUser ();
$session = $user [ 'session' ] ? ? '' ;
$projectId = $this -> getProject ()[ '$id' ];
$client = $this -> getWebsocket ([ 'documents' , 'collections' ], [
'origin' => 'http://localhost' ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
], null );
$response = json_decode ( $client -> receive (), true );
$this -> assertEquals ( 'connected' , $response [ 'type' ]);
2026-04-03 16:42:24 +00:00
// Test Database Create
2026-04-03 16:30:03 +00:00
$database = $this -> client -> call ( Client :: METHOD_POST , '/databases' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'databaseId' => ID :: unique (),
'name' => 'Atomic DB' ,
]);
$databaseId = $database [ 'body' ][ '$id' ];
2026-04-03 16:42:24 +00:00
//Test Collection Create
2026-04-03 16:30:03 +00:00
$actors = $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' => 'Atomic Actors' ,
'permissions' => [
Permission :: create ( Role :: user ( $this -> getUser ()[ '$id' ])),
],
'documentSecurity' => true ,
]);
$actorsId = $actors [ 'body' ][ '$id' ];
2026-04-03 16:42:24 +00:00
//Test Attribute Create
2026-04-08 10:08:32 +00:00
2026-04-03 16:30:03 +00:00
$scoreAttr = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/integer' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ]
]), [
'key' => 'score' ,
'required' => true ,
]);
$this -> assertEventually ( function () use ( $databaseId , $actorsId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/databases/' . $databaseId . '/collections/' . $actorsId . '/attributes/score' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 'available' , $response [ 'body' ][ 'status' ]);
}, 30000 , 250 );
2026-04-03 16:42:24 +00:00
//Test Document Create
2026-04-03 16:30:03 +00:00
$document = $this -> client -> call ( Client :: METHOD_POST , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'documentId' => ID :: unique (),
'data' => [
'score' => 10
],
'permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
]);
$documentId = $document [ 'body' ][ '$id' ];
$client -> receive ();
2026-04-03 16:42:24 +00:00
// Test Document Increment
2026-04-03 16:30:03 +00:00
$increment = $this -> client -> call ( Client :: METHOD_PATCH , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId . '/score/increment' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'value' => 5
]);
2026-04-08 10:08:32 +00:00
2026-04-03 16:30:03 +00:00
$this -> assertEquals ( 200 , $increment [ 'headers' ][ 'status-code' ]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-04-03 16:30:03 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
2026-04-08 10:08:32 +00:00
2026-04-03 16:30:03 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 15 , $response [ 'data' ][ 'payload' ][ 'score' ]);
try {
$client -> receive ();
$this -> fail ( 'Should not receive duplicate event' );
} catch ( TimeoutException $e ) {
chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
2026-04-19 12:01:20 +00:00
$this -> addToAssertionCount ( 1 );
2026-04-03 16:30:03 +00:00
}
2026-04-03 16:42:24 +00:00
// Test Document Decrement
2026-04-03 16:30:03 +00:00
$decrement = $this -> client -> call ( Client :: METHOD_PATCH , '/databases/' . $databaseId . '/collections/' . $actorsId . '/documents/' . $documentId . '/score/decrement' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'value' => 3
]);
$this -> assertEquals ( 200 , $decrement [ 'headers' ][ 'status-code' ]);
$response = json_decode ( $client -> receive (), true );
$this -> assertArrayHasKey ( 'type' , $response );
$this -> assertArrayHasKey ( 'data' , $response );
$this -> assertEquals ( 'event' , $response [ 'type' ]);
$this -> assertNotEmpty ( $response [ 'data' ]);
$this -> assertArrayHasKey ( 'timestamp' , $response [ 'data' ]);
2026-04-27 11:10:15 +00:00
$this -> assertCount ( 16 , $response [ 'data' ][ 'channels' ]);
2026-04-03 16:30:03 +00:00
$this -> assertContains ( " databases. { $databaseId } .collections. { $actorsId } .documents. { $documentId } .update " , $response [ 'data' ][ 'events' ]);
2026-04-08 10:08:32 +00:00
2026-04-03 16:30:03 +00:00
$this -> assertNotEmpty ( $response [ 'data' ][ 'payload' ]);
$this -> assertIsArray ( $response [ 'data' ][ 'payload' ]);
$this -> assertArrayHasKey ( '$id' , $response [ 'data' ][ 'payload' ]);
$this -> assertEquals ( 12 , $response [ 'data' ][ 'payload' ][ 'score' ]);
try {
$client -> receive ();
$this -> fail ( 'Should not receive duplicate event' );
} catch ( TimeoutException $e ) {
chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
2026-04-19 12:01:20 +00:00
$this -> addToAssertionCount ( 1 );
2026-04-03 16:30:03 +00:00
}
$client -> close ();
}
2022-04-18 16:21:45 +00:00
}