appwrite/tests/unit/Messaging/MessagingTest.php

521 lines
17 KiB
PHP
Raw Normal View History

2021-06-29 13:11:14 +00:00
<?php
2022-08-01 10:22:04 +00:00
namespace Tests\Unit\Messaging;
2021-06-29 13:11:14 +00:00
use Appwrite\Messaging\Adapter\Realtime;
use PHPUnit\Framework\TestCase;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Document;
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-06-29 13:11:14 +00:00
class MessagingTest extends TestCase
{
public function setUp(): void
{
}
public function tearDown(): void
{
}
2022-08-01 10:22:04 +00:00
public function testUser(): void
2021-06-29 13:11:14 +00:00
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
2026-02-03 08:46:35 +00:00
ID::unique(),
2022-08-19 04:04:33 +00:00
[
Role::user(ID::custom('123'))->toString(),
Role::users()->toString(),
Role::team(ID::custom('abc'))->toString(),
Role::team(ID::custom('abc'), 'administrator')->toString(),
Role::team(ID::custom('abc'), 'moderator')->toString(),
Role::team(ID::custom('def'))->toString(),
Role::team(ID::custom('def'), 'guest')->toString(),
],
2026-02-03 08:46:35 +00:00
// Pass plain channel names, Realtime::subscribe will normalize them
['files', 'documents', 'documents.789', 'account.123']
2021-06-29 13:11:14 +00:00
);
$event = [
'project' => '1',
2022-08-19 04:04:33 +00:00
'roles' => [Role::any()->toString()],
2021-06-29 13:11:14 +00:00
'data' => [
'channels' => [
0 => 'account.123',
]
2021-06-29 13:11:14 +00:00
]
];
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::users()->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::user(ID::custom('123'))->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('abc'))->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('abc'), 'administrator')->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('abc'), 'moderator')->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('def'))->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('def'), 'guest')->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::user(ID::custom('456'))->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertEmpty($receivers);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::team(ID::custom('def'), 'member')->toString()];
2021-06-29 13:11:14 +00:00
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertEmpty($receivers);
2022-08-19 04:04:33 +00:00
$event['roles'] = [Role::any()->toString()];
2021-06-29 13:11:14 +00:00
$event['data']['channels'] = ['documents.123'];
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertEmpty($receivers);
$event['data']['channels'] = ['documents.789'];
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertCount(1, $receivers);
$this->assertEquals(1, $receivers[0]);
$event['project'] = '2';
2026-01-29 06:08:20 +00:00
$receivers = array_keys($realtime->getSubscribers($event));
2021-06-29 13:11:14 +00:00
$this->assertEmpty($receivers);
$realtime->unsubscribe(2);
$this->assertCount(1, $realtime->connections);
$this->assertCount(7, $realtime->subscriptions['1']);
$realtime->unsubscribe(1);
$this->assertEmpty($realtime->connections);
$this->assertEmpty($realtime->subscriptions);
}
2021-06-30 11:36:58 +00:00
public function testSubscribeUnionsChannelsAndRoles(): void
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::user(ID::custom('123'))->toString()],
['documents'],
);
$realtime->subscribe(
'1',
1,
'sub-b',
[Role::users()->toString()],
['files'],
);
$connection = $realtime->connections[1];
$this->assertContains('documents', $connection['channels']);
$this->assertContains('files', $connection['channels']);
$this->assertContains(Role::user(ID::custom('123'))->toString(), $connection['roles']);
$this->assertContains(Role::users()->toString(), $connection['roles']);
$this->assertCount(2, $connection['channels']);
$this->assertCount(2, $connection['roles']);
}
public function testUnsubscribeSubscriptionRemovesOnlyOneSubscription(): void
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::user(ID::custom('123'))->toString()],
['documents'],
);
$realtime->subscribe(
'1',
1,
'sub-b',
[Role::users()->toString()],
['files'],
);
$removed = $realtime->unsubscribeSubscription(1, 'sub-a');
$this->assertTrue($removed);
$this->assertArrayHasKey(1, $realtime->connections);
// sub-a is fully cleaned from the tree
$this->assertArrayNotHasKey(
Role::user(ID::custom('123'))->toString(),
$realtime->subscriptions['1']
);
// sub-b still delivers
$event = [
'project' => '1',
'roles' => [Role::users()->toString()],
'data' => [
'channels' => ['files'],
],
];
$receivers = array_keys($realtime->getSubscribers($event));
$this->assertEquals([1], $receivers);
// Channels recomputed: sub-a's channel is gone
$this->assertSame(['files'], $realtime->connections[1]['channels']);
// Roles are connection-level auth context — union of both subscribe calls preserved
$this->assertContains(Role::user(ID::custom('123'))->toString(), $realtime->connections[1]['roles']);
$this->assertContains(Role::users()->toString(), $realtime->connections[1]['roles']);
}
public function testUnsubscribeSubscriptionIsIdempotent(): void
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::users()->toString()],
['documents'],
);
$this->assertFalse($realtime->unsubscribeSubscription(1, 'does-not-exist'));
$this->assertFalse($realtime->unsubscribeSubscription(99, 'sub-a'));
// Original sub is untouched
$event = [
'project' => '1',
'roles' => [Role::users()->toString()],
'data' => [
'channels' => ['documents'],
],
];
$this->assertEquals([1], array_keys($realtime->getSubscribers($event)));
}
public function testUnsubscribeSubscriptionKeepsConnectionWhenLastSubRemoved(): void
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::users()->toString()],
['documents'],
);
$this->assertTrue($realtime->unsubscribeSubscription(1, 'sub-a'));
$this->assertArrayHasKey(1, $realtime->connections);
$this->assertSame([], $realtime->connections[1]['channels']);
// Roles preserved so a later resubscribe on the same connection still has auth context
$this->assertSame([Role::users()->toString()], $realtime->connections[1]['roles']);
$this->assertArrayNotHasKey('1', $realtime->subscriptions);
}
public function testResubscribeAfterUnsubscribingLastSubDelivers(): void
{
$realtime = new Realtime();
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::users()->toString()],
['documents'],
);
$this->assertTrue($realtime->unsubscribeSubscription(1, 'sub-a'));
// Simulate the message-based subscribe path reading stored roles
$storedRoles = $realtime->connections[1]['roles'];
$this->assertNotEmpty($storedRoles, 'connection roles must survive per-subscription removal');
$realtime->subscribe('1', 1, 'sub-b', $storedRoles, ['files']);
$event = [
'project' => '1',
'roles' => [Role::users()->toString()],
'data' => [
'channels' => ['files'],
],
];
$this->assertEquals([1], array_keys($realtime->getSubscribers($event)));
}
public function testSubscribeAfterOnOpenEmptySentinelPreservesUnion(): void
{
$realtime = new Realtime();
// Mirrors the onOpen empty-channels path: subscribe with '' id, empty channels
$realtime->subscribe(
'1',
1,
'',
[Role::users()->toString()],
[],
[],
'user-123',
);
// Now a real subscription comes in via the subscribe message type
$realtime->subscribe(
'1',
1,
'sub-a',
[Role::user(ID::custom('user-123'))->toString()],
['documents'],
);
$this->assertSame('user-123', $realtime->connections[1]['userId']);
$this->assertContains('documents', $realtime->connections[1]['channels']);
$this->assertContains(Role::users()->toString(), $realtime->connections[1]['roles']);
$this->assertContains(Role::user(ID::custom('user-123'))->toString(), $realtime->connections[1]['roles']);
}
2022-08-01 10:22:04 +00:00
public function testConvertChannelsGuest(): void
2021-06-30 11:36:58 +00:00
{
$user = new Document([
'$id' => ''
]);
$channels = [
0 => 'files',
1 => 'documents',
2 => 'documents.789',
3 => 'account',
4 => 'account.456'
];
2021-07-13 15:18:02 +00:00
$channels = Realtime::convertChannels($channels, $user->getId());
2021-08-27 08:20:44 +00:00
$this->assertCount(4, $channels);
2021-06-30 11:36:58 +00:00
$this->assertArrayHasKey('files', $channels);
$this->assertArrayHasKey('documents', $channels);
$this->assertArrayHasKey('documents.789', $channels);
2021-08-27 08:20:44 +00:00
$this->assertArrayHasKey('account', $channels);
2021-06-30 11:36:58 +00:00
$this->assertArrayNotHasKey('account.456', $channels);
}
2022-08-01 10:22:04 +00:00
public function testConvertChannelsUser(): void
2021-06-30 11:36:58 +00:00
{
$user = new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('123'),
2021-06-30 11:36:58 +00:00
'memberships' => [
[
2022-08-14 10:33:36 +00:00
'teamId' => ID::custom('abc'),
2021-06-30 11:36:58 +00:00
'roles' => [
'administrator',
'moderator'
]
],
[
2022-08-14 10:33:36 +00:00
'teamId' => ID::custom('def'),
2021-06-30 11:36:58 +00:00
'roles' => [
'guest'
]
]
]
]);
$channels = [
0 => 'files',
1 => 'documents',
2 => 'documents.789',
3 => 'account',
4 => 'account.456'
];
2021-07-13 15:18:02 +00:00
$channels = Realtime::convertChannels($channels, $user->getId());
2021-06-30 11:36:58 +00:00
2021-08-27 08:20:44 +00:00
$this->assertCount(5, $channels);
2021-06-30 11:36:58 +00:00
$this->assertArrayHasKey('files', $channels);
$this->assertArrayHasKey('documents', $channels);
$this->assertArrayHasKey('documents.789', $channels);
$this->assertArrayHasKey('account.123', $channels);
2021-08-27 08:20:44 +00:00
$this->assertArrayHasKey('account', $channels);
2021-06-30 11:36:58 +00:00
$this->assertArrayNotHasKey('account.456', $channels);
}
2021-12-16 18:12:06 +00:00
2022-08-19 04:04:33 +00:00
public function testFromPayloadPermissions(): void
2021-12-16 18:12:06 +00:00
{
/**
* Test Collection Level Permissions
*/
$result = Realtime::fromPayload(
Database layer (#3338) * database response model * database collection config * new database scopes * database service update * database execption codes * remove read write permission from database model * updating tests and fixing some bugs * server side tests are now passing * databases api * tests for database endpoint * composer update * fix error * formatting * formatting fixes * get database test * more updates to events and usage * more usage updates * fix delete type * fix test * delete database * more fixes * databaseId in attributes and indexes * more fixes * fix issues * fix index subquery * fix console scope and index query * updating tests as required * fix phpcs errors and warnings * updates to review suggestions * UI progress * ui updates and cleaning up * fix type * rework database events * update tests * update types * event generation fixed * events config updated * updating context to support multiple * realtime updates * fix ids * update context * validator updates * fix naming conflict * fix tests * fix lint errors * fix wprler and realtime tests * fix webhooks test * fix event validator and other tests * formatting fixes * removing leftover var_dumps * remove leftover comment * update usage params * usage metrics updates * update database usage * fix usage * specs update * updates to usage * fix UI and usage * fix lints * internal id fixes * fixes for internal Id * renaming services and related files * rename tests * rename doc link * rename readme * fix test name * tests: fixes for 0.15.x sync Co-authored-by: Torsten Dittmann <torsten.dittmann@googlemail.com>
2022-06-22 10:51:49 +00:00
event: 'databases.database_id.collections.collection_id.documents.document_id.create',
2021-12-16 18:12:06 +00:00
payload: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('test'),
'$collection' => ID::custom('collection'),
'$permissions' => [
2022-08-19 04:04:33 +00:00
Permission::read(Role::team('123abc')),
Permission::update(Role::team('123abc')),
Permission::delete(Role::team('123abc')),
],
2021-12-16 18:12:06 +00:00
]),
2022-08-13 14:55:15 +00:00
database: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('database'),
2022-08-13 14:55:15 +00:00
]),
2021-12-16 18:12:06 +00:00
collection: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('collection'),
'$permissions' => [
2022-08-14 05:21:11 +00:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2021-12-16 18:12:06 +00:00
])
);
2022-08-19 04:04:33 +00:00
$this->assertContains(Role::any()->toString(), $result['roles']);
$this->assertNotContains(Role::team('123abc')->toString(), $result['roles']);
2021-12-16 18:12:06 +00:00
/**
* Test Document Level Permissions
*/
$result = Realtime::fromPayload(
Database layer (#3338) * database response model * database collection config * new database scopes * database service update * database execption codes * remove read write permission from database model * updating tests and fixing some bugs * server side tests are now passing * databases api * tests for database endpoint * composer update * fix error * formatting * formatting fixes * get database test * more updates to events and usage * more usage updates * fix delete type * fix test * delete database * more fixes * databaseId in attributes and indexes * more fixes * fix issues * fix index subquery * fix console scope and index query * updating tests as required * fix phpcs errors and warnings * updates to review suggestions * UI progress * ui updates and cleaning up * fix type * rework database events * update tests * update types * event generation fixed * events config updated * updating context to support multiple * realtime updates * fix ids * update context * validator updates * fix naming conflict * fix tests * fix lint errors * fix wprler and realtime tests * fix webhooks test * fix event validator and other tests * formatting fixes * removing leftover var_dumps * remove leftover comment * update usage params * usage metrics updates * update database usage * fix usage * specs update * updates to usage * fix UI and usage * fix lints * internal id fixes * fixes for internal Id * renaming services and related files * rename tests * rename doc link * rename readme * fix test name * tests: fixes for 0.15.x sync Co-authored-by: Torsten Dittmann <torsten.dittmann@googlemail.com>
2022-06-22 10:51:49 +00:00
event: 'databases.database_id.collections.collection_id.documents.document_id.create',
2021-12-16 18:12:06 +00:00
payload: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('test'),
'$collection' => ID::custom('collection'),
'$permissions' => [
2022-08-14 05:21:11 +00:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2021-12-16 18:12:06 +00:00
]),
2022-08-13 14:55:15 +00:00
database: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('database'),
2022-08-13 14:55:15 +00:00
]),
2021-12-16 18:12:06 +00:00
collection: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('collection'),
'$permissions' => [
2022-08-19 04:04:33 +00:00
Permission::read(Role::team('123abc')),
Permission::update(Role::team('123abc')),
Permission::delete(Role::team('123abc')),
],
'documentSecurity' => true,
2021-12-16 18:12:06 +00:00
])
);
2022-08-19 04:04:33 +00:00
$this->assertContains(Role::any()->toString(), $result['roles']);
$this->assertContains(Role::team('123abc')->toString(), $result['roles']);
2021-12-16 18:12:06 +00:00
}
2022-04-18 16:21:45 +00:00
public function testFromPayloadBucketLevelPermissions(): void
{
/**
2022-08-13 14:55:15 +00:00
* Test Bucket Level Permissions
2022-04-18 16:21:45 +00:00
*/
$result = Realtime::fromPayload(
event: 'buckets.bucket_id.files.file_id.create',
payload: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('test'),
'$collection' => ID::custom('bucket'),
'$permissions' => [
2022-08-19 04:04:33 +00:00
Permission::read(Role::team('123abc')),
Permission::update(Role::team('123abc')),
Permission::delete(Role::team('123abc')),
],
2022-04-18 16:21:45 +00:00
]),
bucket: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('bucket'),
'$permissions' => [
2022-08-14 05:21:11 +00:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2022-04-18 16:21:45 +00:00
])
);
2022-08-19 04:04:33 +00:00
$this->assertContains(Role::any()->toString(), $result['roles']);
$this->assertNotContains(Role::team('123abc')->toString(), $result['roles']);
2022-04-18 16:21:45 +00:00
/**
2022-08-13 14:55:15 +00:00
* Test File Level Permissions
2022-04-18 16:21:45 +00:00
*/
$result = Realtime::fromPayload(
event: 'buckets.bucket_id.files.file_id.create',
payload: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('test'),
'$collection' => ID::custom('bucket'),
'$permissions' => [
2022-08-14 05:21:11 +00:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2022-04-18 16:21:45 +00:00
]),
bucket: new Document([
2022-08-14 10:33:36 +00:00
'$id' => ID::custom('bucket'),
'$permissions' => [
2022-08-19 04:04:33 +00:00
Permission::read(Role::team('123abc')),
Permission::update(Role::team('123abc')),
Permission::delete(Role::team('123abc')),
],
2022-08-13 14:55:15 +00:00
'fileSecurity' => true
2022-04-18 16:21:45 +00:00
])
);
2022-08-19 04:04:33 +00:00
$this->assertContains(Role::any()->toString(), $result['roles']);
$this->assertContains(Role::team('123abc')->toString(), $result['roles']);
2022-04-18 16:21:45 +00:00
}
2021-06-29 13:11:14 +00:00
}