diff --git a/app/realtime.php b/app/realtime.php index bee946dd02..93d3fa3595 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -8,6 +8,7 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Event\Event; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Network\Validator\Origin; +use Appwrite\Utopia\Response; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Swoole\Runtime; @@ -19,7 +20,6 @@ use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Swoole\Request; -use Utopia\Swoole\Response; use Utopia\WebSocket\Server; use Utopia\WebSocket\Adapter; @@ -206,7 +206,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, foreach ($stats as $projectId => $value) { $event = [ 'project' => 'console', - 'roles' => ['team:'.$value['teamId']], + 'roles' => ['team:' . $value['teamId']], 'data' => [ 'event' => 'stats.connections', 'channels' => ['project'], @@ -217,7 +217,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, ] ]; - $server->send($realtime->getSubscribers($event), json_encode($event['data'])); + $server->send($realtime->getSubscribers($event), json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ])); } $register->get('dbPool')->put($db); @@ -240,7 +243,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, ] ]; - $server->send($realtime->getSubscribers($event), json_encode($event['data'])); + $server->send($realtime->getSubscribers($event), json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ])); } }); @@ -305,7 +311,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $server->send( $receivers, - json_encode($event['data']) + json_encode([ + 'type' => 'event', + 'data' => $event['data'] + ]) ); if (($num = count($receivers)) > 0) { @@ -328,6 +337,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) { $app = new App('UTC'); $request = new Request($request); + $response = new Response(new SwooleResponse()); /** @var PDO $db */ $db = $register->get('dbPool')->get(); @@ -348,8 +358,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, return $request; }); - App::setResource('response', function () { - return new Response(new SwooleResponse()); + App::setResource('response', function () use ($response) { + return $response; }); try { @@ -411,7 +421,15 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $realtime->subscribe($project->getId(), $connection, $roles, $channels); - $server->send([$connection], json_encode($channels)); + $user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_USER); + + $server->send([$connection], json_encode([ + 'type' => 'connected', + 'data' => [ + 'channels' => array_keys($channels), + 'user' => $user + ] + ])); $stats->set($project->getId(), [ 'projectId' => $project->getId(), @@ -421,8 +439,11 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $stats->incr($project->getId(), 'connectionsTotal'); } catch (\Throwable $th) { $response = [ - 'code' => $th->getCode(), - 'message' => $th->getMessage() + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] ]; $server->send([$connection], json_encode($response)); @@ -446,9 +467,99 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server, } }); -$server->onMessage(function (int $connection, string $message) use ($server) { - $server->send([$connection], 'Sending messages is not allowed.'); - $server->close($connection, 1003); +$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) { + try { + $response = new Response(new SwooleResponse()); + $db = $register->get('dbPool')->get(); + $cache = $register->get('redisPool')->get(); + + $projectDB = new Database(); + $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($db, $cache), $cache)); + $projectDB->setNamespace('app_' . $realtime->connections[$connection]['projectId']); + $projectDB->setMocks(Config::getParam('collections', [])); + + /* + * Abuse Check + * + * Abuse limits are sending 32 times per minute and connection. + */ + $timeLimit = new TimeLimit('url:{url},conection:{connection}', 32, 60, $db); + $timeLimit + ->setNamespace('app_' . $realtime->connections[$connection]['projectId']) + ->setParam('{connection}', $connection) + ->setParam('{container}', $containerId); + + $abuse = new Abuse($timeLimit); + + if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') { + throw new Exception('Too many messages', 1013); + } + + $message = json_decode($message, true); + + if (is_null($message) || (!array_key_exists('type', $message) && !array_key_exists('data', $message))) { + throw new Exception('Message format is not valid.', 1003); + } + + switch ($message['type']) { + case 'authentication': + if (!array_key_exists('session', $message['data'])) { + throw new Exception('Payload is not valid.', 1003); + } + + $session = Auth::decodeSession($message['data']['session']); + Auth::$unique = $session['id']; + Auth::$secret = $session['secret']; + + $user = $projectDB->getDocument(Auth::$unique); + + if ( + empty($user->getId()) // Check a document has been found in the DB + || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document + || !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token + ) { + // cookie not valid + throw new Exception('Session is not valid.', 1003); + } + + $roles = Auth::getRoles($user); + $channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId()); + $realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels); + + $user = $response->output($user, Response::MODEL_USER); + $server->send([$connection], json_encode([ + 'type' => 'response', + 'data' => [ + 'to' => 'authentication', + 'success' => true, + 'user' => $user + ] + ])); + + break; + + default: + throw new Exception('Message type is not valid.', 1003); + break; + } + } catch (\Throwable $th) { + $response = [ + 'type' => 'error', + 'data' => [ + 'code' => $th->getCode(), + 'message' => $th->getMessage() + ] + ]; + + $server->send([$connection], json_encode($response)); + + if ($th->getCode() === 1008) { + $server->close($connection, $th->getCode()); + } + } finally { + $register->get('dbPool')->put($db); + $register->get('redisPool')->put($cache); + } }); $server->onClose(function (int $connection) use ($realtime, $stats) { diff --git a/src/Appwrite/Messaging/Adapter/Realtime.php b/src/Appwrite/Messaging/Adapter/Realtime.php index 11eca4ade7..5f03009fc4 100644 --- a/src/Appwrite/Messaging/Adapter/Realtime.php +++ b/src/Appwrite/Messaging/Adapter/Realtime.php @@ -226,18 +226,10 @@ class Realtime extends Adapter if (!empty($userId)) { $channels['account.' . $userId] = $value; } - unset($channels['account']); break; } } - if (\array_key_exists('account', $channels)) { - if ($userId) { - $channels['account.' . $userId] = $channels['account']; - } - unset($channels['account']); - } - return $channels; } diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php index b9f4f5f84a..d1afacb7a7 100644 --- a/tests/e2e/Services/Realtime/RealtimeBase.php +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -32,9 +32,7 @@ trait RealtimeBase * Test for SUCCESS */ $client = $this->getWebsocket(['documents']); - $this->assertEquals(json_encode([ - 'documents' => 0 - ]), $client->receive()); + $this->assertNotEmpty($client->receive()); $client->close(); /** @@ -42,15 +40,23 @@ trait RealtimeBase */ $client = $this->getWebsocket(['documents'], ['origin' => 'http://appwrite.unknown']); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Invalid Origin. Register your new client (appwrite.unknown) as a new Web platform on your project console dashboard', $payload['message']); + + $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']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); $client = $this->getWebsocket(); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing channels', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing channels', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); @@ -60,8 +66,12 @@ trait RealtimeBase ] ]); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing or unknown project ID', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing or unknown project ID', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); @@ -71,8 +81,12 @@ trait RealtimeBase ] ]); $payload = json_decode($client->receive(), true); - $this->assertEquals(1008, $payload['code']); - $this->assertEquals('Missing or unknown project ID', $payload['message']); + + $this->assertArrayHasKey('type', $payload); + $this->assertArrayHasKey('data', $payload); + $this->assertEquals('error', $payload['type']); + $this->assertEquals(1008, $payload['data']['code']); + $this->assertEquals('Missing or unknown project ID', $payload['data']['message']); $this->expectException(ConnectionException::class); // Check if server disconnnected client $client->close(); } @@ -87,23 +101,49 @@ trait RealtimeBase 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session ]; - $client = $this->getWebsocket(['documents']); + $client = $this->getWebsocket(['documents'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('documents', $response); + + $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']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + $client->close(); $client = $this->getWebsocket(['account'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('account.' . $userId, $response); + + $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(); $client = $this->getWebsocket(['account', 'documents', 'account.123'], $headers); $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('account.' . $userId, $response); + + $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']); + $this->assertContains('documents', $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(); $client = $this->getWebsocket([ @@ -119,20 +159,135 @@ trait RealtimeBase 'documents.1', 'documents.2', ], $headers); + $response = json_decode($client->receive(), true); - $this->assertCount(11, $response); - $this->assertArrayHasKey('account.' . $userId, $response); - $this->assertArrayHasKey('files', $response); - $this->assertArrayHasKey('files.1', $response); - $this->assertArrayHasKey('collections', $response); - $this->assertArrayHasKey('collections.1', $response); - $this->assertArrayHasKey('collections.1.documents', $response); - $this->assertArrayHasKey('collections.2', $response); - $this->assertArrayHasKey('collections.2.documents', $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('documents.1', $response); - $this->assertArrayHasKey('documents.2', $response); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('connected', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertNotEmpty($response['data']['user']); + $this->assertCount(12, $response['data']['channels']); + $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']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertContains('collections.1', $response['data']['channels']); + $this->assertContains('collections.1.documents', $response['data']['channels']); + $this->assertContains('collections.2', $response['data']['channels']); + $this->assertContains('collections.2.documents', $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.1', $response['data']['channels']); + $this->assertContains('documents.2', $response['data']['channels']); + $this->assertEquals($userId, $response['data']['user']['$id']); + + $client->close(); + } + + public function testManualAuthentication() + { + $user = $this->getUser(); + $userId = $user['$id'] ?? ''; + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + /** + * 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']); + $this->assertEquals('Payload is not valid.', $response['data']['message']); + + $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(); } @@ -148,8 +303,16 @@ trait RealtimeBase 'cookie' => 'a_session_'.$projectId.'=' . $session ]); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('account.' . $userId, $response); + + $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 @@ -167,14 +330,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.name', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.name', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($name, $response['payload']['name']); + $this->assertEquals($name, $response['data']['payload']['name']); /** @@ -192,14 +359,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.password', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.password', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($name, $response['payload']['name']); + $this->assertEquals($name, $response['data']['payload']['name']); /** * Test Account Email Update @@ -216,14 +387,18 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.email', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.email', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals('torsten@appwrite.io', $response['payload']['email']); + $this->assertEquals('torsten@appwrite.io', $response['data']['payload']['email']); /** * Test Account Verification Create @@ -239,11 +414,15 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.verification.create', $response['event']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.verification.create', $response['data']['event']); $lastEmail = $this->getLastEmail(); $verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); @@ -263,11 +442,15 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.verification.update', $response['event']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.verification.update', $response['data']['event']); /** * Test Acoount Prefs Update @@ -286,12 +469,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.update.prefs', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.update.prefs', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Session Create @@ -310,12 +497,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.sessions.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.sessions.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Session Delete @@ -329,12 +520,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.sessions.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.sessions.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Account Create Recovery @@ -353,12 +548,16 @@ trait RealtimeBase $lastEmail = $this->getLastEmail(); $recovery = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.recovery.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.recovery.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ 'origin' => 'http://localhost', @@ -373,12 +572,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(2, $response['channels']); - $this->assertArrayHasKey('timestamp', $response); - $this->assertContains('account', $response['channels']); - $this->assertContains('account.' . $userId, $response['channels']); - $this->assertEquals('account.recovery.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertContains('account', $response['data']['channels']); + $this->assertContains('account.' . $userId, $response['data']['channels']); + $this->assertEquals('account.recovery.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -393,10 +596,18 @@ trait RealtimeBase 'origin' => 'http://localhost', 'cookie' => 'a_session_'.$projectId.'=' . $session ]); + $response = json_decode($client->receive(), true); - $this->assertCount(2, $response); - $this->assertArrayHasKey('documents', $response); - $this->assertArrayHasKey('collections', $response); + + $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 Collection Create @@ -431,12 +642,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('collections', $response['channels']); - $this->assertContains('collections.' . $actors['body']['$id'], $response['channels']); - $this->assertEquals('database.collections.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('collections', $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'], $response['data']['channels']); + $this->assertEquals('database.collections.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data = ['actorsId' => $actors['body']['$id']]; @@ -457,13 +672,17 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $document['body']['$id'], $response['channels']); - $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data['documentId'] = $document['body']['$id']; @@ -484,16 +703,20 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $data['documentId'], $response['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $data['documentId'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertEquals($response['payload']['firstName'], 'Chris1'); - $this->assertEquals($response['payload']['lastName'], 'Evans2'); + $this->assertEquals($response['data']['payload']['firstName'], 'Chris1'); + $this->assertEquals($response['data']['payload']['lastName'], 'Evans2'); /** * Test Document Delete @@ -520,13 +743,17 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('documents', $response['channels']); - $this->assertContains('documents.' . $document['body']['$id'], $response['channels']); - $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['channels']); - $this->assertEquals('database.documents.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('documents', $response['data']['channels']); + $this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']); + $this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']); + $this->assertEquals('database.documents.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -542,8 +769,15 @@ trait RealtimeBase 'cookie' => 'a_session_'.$projectId.'=' . $session ]); $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('files', $response); + + $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']); /** * Test File Create @@ -560,12 +794,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $data = ['fileId' => $file['body']['$id']]; @@ -582,12 +820,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test File Delete @@ -599,12 +841,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('files', $response['channels']); - $this->assertContains('files.' . $file['body']['$id'], $response['channels']); - $this->assertEquals('storage.files.delete', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('files', $response['data']['channels']); + $this->assertContains('files.' . $file['body']['$id'], $response['data']['channels']); + $this->assertEquals('storage.files.delete', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } @@ -619,9 +865,17 @@ trait RealtimeBase 'origin' => 'http://localhost', 'cookie' => 'a_session_'.$projectId.'=' . $session ]); + $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('executions', $response); + + $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 File Create @@ -678,21 +932,29 @@ trait RealtimeBase $response = json_decode($client->receive(), true); $responseUpdate = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(3, $response['channels']); - $this->assertContains('executions', $response['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $response['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $response['channels']); - $this->assertEquals('functions.executions.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(3, $response['data']['channels']); + $this->assertContains('executions', $response['data']['channels']); + $this->assertContains('executions.' . $execution['body']['$id'], $response['data']['channels']); + $this->assertContains('functions.' . $execution['body']['functionId'], $response['data']['channels']); + $this->assertEquals('functions.executions.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); - $this->assertArrayHasKey('timestamp', $responseUpdate); - $this->assertCount(3, $responseUpdate['channels']); - $this->assertContains('executions', $responseUpdate['channels']); - $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['channels']); - $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['channels']); - $this->assertEquals('functions.executions.update', $responseUpdate['event']); - $this->assertNotEmpty($responseUpdate['payload']); + $this->assertArrayHasKey('type', $responseUpdate); + $this->assertArrayHasKey('data', $responseUpdate); + $this->assertEquals('event', $responseUpdate['type']); + $this->assertNotEmpty($responseUpdate['data']); + $this->assertArrayHasKey('timestamp', $responseUpdate['data']); + $this->assertCount(3, $responseUpdate['data']['channels']); + $this->assertContains('executions', $responseUpdate['data']['channels']); + $this->assertContains('executions.' . $execution['body']['$id'], $responseUpdate['data']['channels']); + $this->assertContains('functions.' . $execution['body']['functionId'], $responseUpdate['data']['channels']); + $this->assertEquals('functions.executions.update', $responseUpdate['data']['event']); + $this->assertNotEmpty($responseUpdate['data']['payload']); $client->close(); } @@ -710,8 +972,14 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('teams', $response); + $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 @@ -730,12 +998,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('teams', $response['channels']); - $this->assertContains('teams.' . $teamId, $response['channels']); - $this->assertEquals('teams.create', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('teams', $response['data']['channels']); + $this->assertContains('teams.' . $teamId, $response['data']['channels']); + $this->assertEquals('teams.create', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); /** * Test Team Update @@ -752,12 +1024,16 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('teams', $response['channels']); - $this->assertContains('teams.' . $teamId, $response['channels']); - $this->assertEquals('teams.update', $response['event']); - $this->assertNotEmpty($response['payload']); + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('teams', $response['data']['channels']); + $this->assertContains('teams.' . $teamId, $response['data']['channels']); + $this->assertEquals('teams.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); @@ -782,8 +1058,14 @@ trait RealtimeBase $response = json_decode($client->receive(), true); - $this->assertCount(1, $response); - $this->assertArrayHasKey('memberships', $response); + $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']); $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamId.'/memberships', array_merge([ 'content-type' => 'application/json', @@ -805,12 +1087,17 @@ trait RealtimeBase ]); $response = json_decode($client->receive(), true); - $this->assertArrayHasKey('timestamp', $response); - $this->assertCount(2, $response['channels']); - $this->assertContains('memberships', $response['channels']); - $this->assertContains('memberships.' . $membershipId, $response['channels']); - $this->assertEquals('teams.memberships.update', $response['event']); - $this->assertNotEmpty($response['payload']); + + $this->assertArrayHasKey('type', $response); + $this->assertArrayHasKey('data', $response); + $this->assertEquals('event', $response['type']); + $this->assertNotEmpty($response['data']); + $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertCount(2, $response['data']['channels']); + $this->assertContains('memberships', $response['data']['channels']); + $this->assertContains('memberships.' . $membershipId, $response['data']['channels']); + $this->assertEquals('teams.memberships.update', $response['data']['event']); + $this->assertNotEmpty($response['data']['payload']); $client->close(); } diff --git a/tests/unit/Messaging/MessagingTest.php b/tests/unit/Messaging/MessagingTest.php index 0d7aa4c3ab..19285d0307 100644 --- a/tests/unit/Messaging/MessagingTest.php +++ b/tests/unit/Messaging/MessagingTest.php @@ -149,11 +149,11 @@ class MessagingTest extends TestCase ]; $channels = Realtime::convertChannels($channels, $user->getId()); - $this->assertCount(3, $channels); + $this->assertCount(4, $channels); $this->assertArrayHasKey('files', $channels); $this->assertArrayHasKey('documents', $channels); $this->assertArrayHasKey('documents.789', $channels); - $this->assertArrayNotHasKey('account', $channels); + $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } @@ -187,12 +187,12 @@ class MessagingTest extends TestCase $channels = Realtime::convertChannels($channels, $user->getId()); - $this->assertCount(4, $channels); + $this->assertCount(5, $channels); $this->assertArrayHasKey('files', $channels); $this->assertArrayHasKey('documents', $channels); $this->assertArrayHasKey('documents.789', $channels); $this->assertArrayHasKey('account.123', $channels); - $this->assertArrayNotHasKey('account', $channels); + $this->assertArrayHasKey('account', $channels); $this->assertArrayNotHasKey('account.456', $channels); } }