diff --git a/composer.json b/composer.json index b4370e7c2f..1bfef772d2 100644 --- a/composer.json +++ b/composer.json @@ -63,6 +63,7 @@ "appwrite/sdk-generator": "0.5.5", "phpunit/phpunit": "9.4.2", "swoole/ide-helper": "4.5.5", + "textalk/websocket": "1.5.2", "vimeo/psalm": "4.1.1" }, "repositories": [ diff --git a/composer.lock b/composer.lock index 85b0932c93..d41a118256 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4f58de92fb64af44d915387895472881", + "content-hash": "7f55eb794a021679abe652b85e3c0134", "packages": [ { "name": "adhocore/jwt", @@ -349,7 +349,7 @@ "issues": "https://github.com/domnikl/statsd-php/issues", "source": "https://github.com/domnikl/statsd-php/tree/master" }, - "abandoned": true, + "abandoned": "slickdeals/statsd", "time": "2020-01-03T14:24:58+00:00" }, { @@ -577,12 +577,12 @@ "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f47ece9e6e8ce74e3be04bef47f46061dc18c095" + "reference": "2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f47ece9e6e8ce74e3be04bef47f46061dc18c095", - "reference": "f47ece9e6e8ce74e3be04bef47f46061dc18c095", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976", + "reference": "2f3e4f6cf8fd4aad7624c90a94f0ab38fde25976", "shasum": "" }, "require": { @@ -644,7 +644,7 @@ "issues": "https://github.com/guzzle/psr7/issues", "source": "https://github.com/guzzle/psr7/tree/1.x" }, - "time": "2020-12-08T11:45:39+00:00" + "time": "2021-03-02T18:57:24+00:00" }, { "name": "influxdb/influxdb-php", @@ -1020,12 +1020,12 @@ "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "dd738d0b4491f32725492cf345f6b501f5922fec" + "reference": "a18c1e692e02b84abbafe4856c3cd7cc6903908c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/dd738d0b4491f32725492cf345f6b501f5922fec", - "reference": "dd738d0b4491f32725492cf345f6b501f5922fec", + "url": "https://api.github.com/repos/php-fig/log/zipball/a18c1e692e02b84abbafe4856c3cd7cc6903908c", + "reference": "a18c1e692e02b84abbafe4856c3cd7cc6903908c", "shasum": "" }, "require": { @@ -1063,7 +1063,7 @@ "support": { "source": "https://github.com/php-fig/log/tree/master" }, - "time": "2020-09-18T06:44:51+00:00" + "time": "2021-03-02T15:02:34+00:00" }, { "name": "ralouphie/getallheaders", @@ -5038,6 +5038,86 @@ ], "time": "2021-02-23T10:10:15+00:00" }, + { + "name": "symfony/polyfill-ctype", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "dev-main", @@ -5614,6 +5694,55 @@ ], "time": "2021-02-17T15:27:35+00:00" }, + { + "name": "textalk/websocket", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/Textalk/websocket-php.git", + "reference": "b93249453806a2dd46495de46d76fcbcb0d8dee8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Textalk/websocket-php/zipball/b93249453806a2dd46495de46d76fcbcb0d8dee8", + "reference": "b93249453806a2dd46495de46d76fcbcb0d8dee8", + "shasum": "" + }, + "require": { + "php": "^7.2 | ^8.0", + "psr/log": "^1.0" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.0", + "phpunit/phpunit": "^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "WebSocket\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Fredrik Liljegren" + }, + { + "name": "Sören Jensen", + "email": "soren@abicart.se" + } + ], + "description": "WebSocket client and server", + "support": { + "issues": "https://github.com/Textalk/websocket-php/issues", + "source": "https://github.com/Textalk/websocket-php/tree/1.5.2" + }, + "time": "2021-02-12T15:39:23+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.0", @@ -5848,6 +5977,65 @@ }, "time": "2020-11-02T05:54:12+00:00" }, + { + "name": "webmozart/assert", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "4631e2c7d2d7132adac9fd84d4c1a98c10a6e049" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/4631e2c7d2d7132adac9fd84d4c1a98c10a6e049", + "reference": "4631e2c7d2d7132adac9fd84d4c1a98c10a6e049", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/master" + }, + "time": "2021-02-28T20:01:57+00:00" + }, { "name": "webmozart/path-util", "version": "dev-master", diff --git a/tests/e2e/Services/Realtime/RealtimeBase.php b/tests/e2e/Services/Realtime/RealtimeBase.php new file mode 100644 index 0000000000..379f21a022 --- /dev/null +++ b/tests/e2e/Services/Realtime/RealtimeBase.php @@ -0,0 +1,593 @@ + 'appwrite.test' + ], $headers); + + $query = [ + 'project' => $this->getProject()['$id'], + 'channels' => $channels + ]; + return new WebSocketClient('ws://appwrite-traefik/v1/realtime?' . http_build_query($query), [ + 'headers' => $headers, + 'timeout' => 5, + ]); + } + + public function testConnection() + { + /** + * Test for SUCCESS + */ + $client = $this->getWebsocket(['documents']); + $this->assertEquals(json_encode([ + 'documents' => 0 + ]), $client->receive()); + $client->close(); + + /** + * Test for FAILURE + */ + $client = $this->getWebsocket(['documents'], ['origin' => 'http://appwrite.unknown']); + $this->assertEquals('Invalid Origin. Register your new client (appwrite.unknown) as a new Web platform on your project console dashboard', $client->receive()); + $this->expectException(ConnectionException::class); // Check if server disconnnected client + $client->close(); + + $client = $this->getWebsocket(); + $this->assertEquals('Missing channels', $client->receive()); + $this->expectException(ConnectionException::class); // Check if server disconnnected client + $client->close(); + + $client = new WebSocketClient('ws://appwrite-traefik/v1/realtime', [ + 'headers' => [ + 'Origin' => 'appwrite.test' + ] + ]); + $this->assertEquals('Missing or unknown project ID', $client->receive()); + $this->expectException(ConnectionException::class); // Check if server disconnnected client + $client->close(); + + $client = new WebSocketClient('ws://appwrite-traefik/v1/realtime?project=123', [ + 'headers' => [ + 'Origin' => 'appwrite.test' + ] + ]); + $this->assertEquals('Missing or unknown project ID', $client->receive()); + $this->expectException(ConnectionException::class); // Check if server disconnnected client + $client->close(); + } + + public function testChannelParsing() + { + $user = $this->getUser(); + $userId = $user['$id'] ?? ''; + $session = $user['session'] ?? ''; + $headers = [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session + ]; + + $client = $this->getWebsocket(['documents']); + $response = json_decode($client->receive(), true); + $this->assertCount(1, $response); + $this->assertArrayHasKey('documents', $response); + $client->close(); + + $client = $this->getWebsocket(['account'], $headers); + $response = json_decode($client->receive(), true); + $this->assertCount(1, $response); + $this->assertArrayHasKey('account.' . $userId, $response); + $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); + $client->close(); + + $client = $this->getWebsocket([ + 'account', + 'files', + 'files.1', + 'collections', + 'collections.1', + 'collections.1.documents', + 'collections.2', + 'collections.2.documents', + 'documents', + '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); + $client->close(); + } + + public function testChannelAccount() + { + $user = $this->getUser(); + $userId = $user['$id'] ?? ''; + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['account'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$projectId.'=' . $session + ]); + $response = json_decode($client->receive(), true); + $this->assertCount(1, $response); + $this->assertArrayHasKey('account.' . $userId, $response); + + /** + * Test Account Name Event + */ + $name = "Torsten Dittmann"; + + $this->client->call(Client::METHOD_PATCH, '/account/name', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_' . $projectId . '=' . $session, + ]), [ + 'name' => $name + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.update.name', $response['event']); + $this->assertNotEmpty($response['payload']); + + $this->assertEquals($name, $response['payload']['name']); + + + /** + * Test Account Password Event + */ + $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $session, + ]), [ + 'password' => 'new-password', + 'oldPassword' => 'password', + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.update.password', $response['event']); + $this->assertNotEmpty($response['payload']); + + $this->assertEquals($name, $response['payload']['name']); + + /** + * Test Account Email Update + */ + $this->client->call(Client::METHOD_PATCH, '/account/email', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $session, + ]), [ + 'email' => 'torsten@appwrite.io', + 'password' => 'new-password', + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.update.email', $response['event']); + $this->assertNotEmpty($response['payload']); + + $this->assertEquals('torsten@appwrite.io', $response['payload']['email']); + + /** + * Test Account Verification Create + */ + $this->client->call(Client::METHOD_POST, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $session, + ]), [ + 'url' => 'http://localhost/verification', + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.verification.create', $response['event']); + + $lastEmail = $this->getLastEmail(); + $verification = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + + /** + * Test Account Verification Complete + */ + $response = $this->client->call(Client::METHOD_PUT, '/account/verification', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $session, + ]), [ + 'userId' => $userId, + 'secret' => $verification, + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.verification.update', $response['event']); + + /** + * Test Acoount Prefs Update + */ + $this->client->call(Client::METHOD_PATCH, '/account/prefs', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $session, + ]), [ + 'prefs' => [ + 'prefKey1' => 'prefValue1', + 'prefKey2' => 'prefValue2', + ] + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.update.prefs', $response['event']); + $this->assertNotEmpty($response['payload']); + + /** + * Test Account Session Create + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ]), [ + 'email' => 'torsten@appwrite.io', + 'password' => 'new-password', + ]); + + $sessionNew = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$projectId]; + $sessionNewId = $response['body']['$id']; + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.sessions.create', $response['event']); + $this->assertNotEmpty($response['payload']); + + /** + * Test Account Session Delete + */ + $this->client->call(Client::METHOD_DELETE, '/account/sessions/'.$sessionNewId, array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'cookie' => 'a_session_'.$projectId.'=' . $sessionNew, + ])); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.sessions.delete', $response['event']); + $this->assertNotEmpty($response['payload']); + + /** + * Test Account Create Recovery + */ + $this->client->call(Client::METHOD_POST, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ]), [ + 'email' => 'torsten@appwrite.io', + 'url' => 'http://localhost/recovery', + ]); + + $response = json_decode($client->receive(), true); + + $lastEmail = $this->getLastEmail(); + $recovery = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.recovery.create', $response['event']); + $this->assertNotEmpty($response['payload']); + + $response = $this->client->call(Client::METHOD_PUT, '/account/recovery', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + ]), [ + 'userId' => $userId, + 'secret' => $recovery, + 'password' => 'test-recovery', + 'passwordAgain' => 'test-recovery', + ]); + + $response = json_decode($client->receive(), true); + + $this->assertCount(1, $response['channels']); + $this->assertArrayHasKey('timestamp', $response); + $this->assertEquals('account.' . $userId, $response['channels'][0]); + $this->assertEquals('account.recovery.update', $response['event']); + $this->assertNotEmpty($response['payload']); + + $client->close(); + } + + public function testChannelDatabase() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['documents', 'collections'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$projectId.'=' . $session + ]); + $response = json_decode($client->receive(), true); + $this->assertCount(2, $response); + $this->assertArrayHasKey('documents', $response); + $this->assertArrayHasKey('collections', $response); + + /** + * Test Collection Create + */ + $actors = $this->client->call(Client::METHOD_POST, '/database/collections', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'] + ]), [ + 'name' => 'Actors', + 'read' => ['*'], + 'write' => ['*'], + 'rules' => [ + [ + 'label' => 'First Name', + 'key' => 'firstName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + [ + 'label' => 'Last Name', + 'key' => 'lastName', + 'type' => 'text', + 'default' => '', + 'required' => true, + 'array' => false + ], + ], + ]); + + $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']); + + $data = ['actorsId' => $actors['body']['$id']]; + + /** + * Test Document Create + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Chris', + 'lastName' => 'Evans', + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $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']); + + $data['documentId'] = $document['body']['$id']; + + /** + * Test Document Update + */ + $document = $this->client->call(Client::METHOD_PATCH, '/database/collections/' . $data['actorsId'] . '/documents/' . $data['documentId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Chris1', + 'lastName' => 'Evans2', + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $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->assertEquals($response['payload']['firstName'], 'Chris1'); + $this->assertEquals($response['payload']['lastName'], 'Evans2'); + + /** + * Test Document Delete + */ + $document = $this->client->call(Client::METHOD_POST, '/database/collections/' . $data['actorsId'] . '/documents', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'data' => [ + 'firstName' => 'Bradly', + 'lastName' => 'Cooper', + + ], + 'read' => ['*'], + 'write' => ['*'], + ]); + + $client->receive(); + + $this->client->call(Client::METHOD_DELETE, '/database/collections/' . $data['actorsId'] . '/documents/' . $document['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $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']); + + $client->close(); + } + + public function testChannelFiles() + { + $user = $this->getUser(); + $session = $user['session'] ?? ''; + $projectId = $this->getProject()['$id']; + + $client = $this->getWebsocket(['files'], [ + 'origin' => 'http://localhost', + 'cookie' => 'a_session_'.$projectId.'=' . $session + ]); + $response = json_decode($client->receive(), true); + $this->assertCount(1, $response); + $this->assertArrayHasKey('files', $response); + + /** + * Test File Create + */ + $file = $this->client->call(Client::METHOD_POST, '/storage/files', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), + 'read' => ['*'], + 'write' => ['*'], + 'folderId' => 'xyz', + ]); + + $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']); + + $data = ['fileId' => $file['body']['$id']]; + + /** + * Test File Update + */ + $this->client->call(Client::METHOD_PUT, '/storage/files/' . $data['fileId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'read' => ['*'], + 'write' => ['*'], + ]); + + $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']); + + /** + * Test File Delete + */ + $this->client->call(Client::METHOD_DELETE, '/storage/files/' . $data['fileId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $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']); + + $client->close(); + } +} diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php new file mode 100644 index 0000000000..8822c493b2 --- /dev/null +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -0,0 +1,15 @@ +