Merge pull request #10096 from ArnabChatterjee20k/dat-556

Realtime support for bulk api
This commit is contained in:
Jake Barnby 2025-08-05 20:26:43 +12:00 committed by GitHub
commit 46431118b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 867 additions and 7 deletions

View file

@ -383,6 +383,8 @@ class Event
{
$this->params = [];
$this->sensitive = [];
$this->event = '';
$this->payload = [];
return $this;
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Utopia\Database\Database;
use Utopia\Database\Document;
@ -279,4 +280,59 @@ abstract class Action extends UtopiaAction
return true;
}
/**
* For triggering different queues for each document for a bulk documents
* @param string $event
* @param Document $database
* @param Document $collection
* @param Document[] $documents
* @param Event $queueForEvents
* @param Event $queueForRealtime
* @param Event $queueForFunctions
* @param Event $queueForWebhooks
* @return void
*/
protected function triggerBulk(
string $event,
Document $database,
Document $collection,
array $documents,
Event $queueForEvents,
Event $queueForRealtime,
Event $queueForFunctions,
Event $queueForWebhooks
): void {
$queueForEvents
->setEvent($event)
->setParam('databaseId', $database->getId())
->setContext('database', $database)
->setParam('collectionId', $collection->getId())
->setParam('tableId', $collection->getId())
->setContext($this->getCollectionsEventsContext(), $collection);
foreach ($documents as $document) {
$queueForEvents
->setParam('documentId', $document->getId())
->setParam('rowId', $document->getId())
->setPayload($document->getArrayCopy());
$queueForRealtime
->from($queueForEvents)
->trigger();
$queueForFunctions
->from($queueForEvents)
->trigger();
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
$queueForEvents->reset();
$queueForRealtime->reset();
$queueForFunctions->reset();
$queueForWebhooks->reset();
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
@ -72,11 +73,15 @@ class Delete extends Action
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void
public function action(string $databaseId, string $collectionId, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
{
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
@ -134,5 +139,16 @@ class Delete extends Action
'total' => $modified,
$this->getSdkGroup() => $documents,
]), $this->getResponseModel());
$this->triggerBulk(
'databases.[databaseId].collections.[collectionId].documents.[documentId].delete',
$database,
$collection,
$documents,
$queueForEvents,
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks
);
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Bulk;
use Appwrite\Event\Event;
use Appwrite\Event\StatsUsage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Databases\Http\Databases\Collections\Documents\Action;
@ -76,11 +77,15 @@ class Update extends Action
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan): void
public function action(string $databaseId, string $collectionId, string|array $data, array $queries, UtopiaResponse $response, Database $dbForProject, StatsUsage $queueForStatsUsage, Event $queueForEvents, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, array $plan): void
{
$data = \is_string($data)
? \json_decode($data, true)
@ -158,5 +163,16 @@ class Update extends Action
'total' => $modified,
$this->getSdkGroup() => $documents
]), $this->getResponseModel());
$this->triggerBulk(
'databases.[databaseId].collections.[collectionId].documents.[documentId].update',
$database,
$collection,
$documents,
$queueForEvents,
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks
);
}
}

View file

@ -122,10 +122,12 @@ class Create extends Action
->inject('user')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->callback($this->action(...));
}
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage): void
public function action(string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, ?array $documents, UtopiaResponse $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks): void
{
$data = \is_string($data)
? \json_decode($data, true)
@ -417,6 +419,16 @@ class Create extends Action
$this->getSdkGroup() => $documents
]), $this->getBulkResponseModel());
$this->triggerBulk(
'databases.[databaseId].collections.[collectionId].documents.[documentId].create',
$database,
$collection,
$documents,
$queueForEvents,
$queueForRealtime,
$queueForFunctions,
$queueForWebhooks
);
return;
}

View file

@ -59,6 +59,10 @@ class Delete extends DocumentsDelete
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}

View file

@ -61,6 +61,10 @@ class Update extends DocumentsUpdate
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('queueForEvents')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->inject('plan')
->callback($this->action(...));
}

View file

@ -101,6 +101,9 @@ class Create extends DocumentCreate
->inject('user')
->inject('queueForEvents')
->inject('queueForStatsUsage')
->inject('queueForRealtime')
->inject('queueForFunctions')
->inject('queueForWebhooks')
->callback($this->action(...));
}
}

View file

@ -182,9 +182,9 @@ abstract class Scope extends TestCase
/**
* @return array
*/
public function getUser(): array
public function getUser(bool $fresh = false): array
{
if (isset(self::$user[$this->getProject()['$id']])) {
if (!$fresh && isset(self::$user[$this->getProject()['$id']])) {
return self::$user[$this->getProject()['$id']];
}

View file

@ -3,6 +3,7 @@
namespace Tests\E2E\Services\Realtime;
use CURLFile;
use Exception;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
@ -12,6 +13,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use WebSocket\ConnectionException;
use WebSocket\TimeoutException;
class RealtimeCustomClientTest extends Scope
{
@ -819,7 +821,6 @@ class RealtimeCustomClientTest extends Scope
]);
$response = json_decode($client->receive(), true);
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);
$this->assertEquals('event', $response['type']);
@ -898,9 +899,755 @@ class RealtimeCustomClientTest extends Scope
$this->assertNotEmpty($response['data']['payload']);
$this->assertEquals('Bradley Cooper', $response['data']['payload']['name']);
// 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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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' => [
'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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$this->assertCount(6, $response['data']['channels']);
$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']);
$client->close();
}
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']);
$user2 = $this->getUser(true);
$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']);
sleep(2);
// 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(),
'name' => 'User2',
'$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']);
$this->assertCount(6, $response1['data']['channels']);
$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']);
$this->assertCount(6, $response2['data']['channels']);
$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']);
}
// Perform bulk update(making it only accessible by user1)
$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($user1Id)),
Permission::update(Role::user($user1Id)),
Permission::delete(Role::user($user1Id)),
]
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
// Receive and assert for client1
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']);
$this->assertCount(6, $response1['data']['channels']);
$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']);
}
// 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']);
$this->assertCount(6, $response2['data']['channels']);
$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']);
$this->assertCount(6, $response1['data']['channels']);
$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']);
$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']);
$this->assertCount(6, $response2['data']['channels']);
$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']);
// Receive and assert for client1
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']);
$this->assertCount(6, $response1['data']['channels']);
$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']);
}
// 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']);
$this->assertCount(6, $response2['data']['channels']);
$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']);
}
$client1->close();
$client2->close();
}
public function testChannelDatabaseCollectionPermissions()
{
$user = $this->getUser();