From 9ae96448ce3357bcb94f034e25751bede9505c9d Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Sun, 22 Feb 2026 16:22:44 +0000 Subject: [PATCH 1/3] added site migration --- composer.json | 17 +++++- composer.lock | 52 ++++++++++++------- src/Appwrite/Platform/Workers/Migrations.php | 2 + .../Utopia/Response/Model/MigrationReport.php | 18 +++++++ 4 files changed, 67 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index ce55926d23b..0c8fc869be3 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.20.*", - "utopia-php/migration": "1.6.*", + "utopia-php/migration": "1.6.x-dev", "utopia-php/platform": "0.7.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", @@ -109,5 +109,18 @@ "php-http/discovery": true, "tbachert/spi": true } - } + }, + "repositories": [ + { + "type": "path", + "url": "../utopia-migration", + "options": { + "versions": { + "utopia-php/migration": "1.6.x-dev" + } + } + } + ], + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/composer.lock b/composer.lock index f17670f7b1e..031bf10a1b7 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": "dd9887a50fb434887461a99413007573", + "content-hash": "9e05c1a1869d52d4e7679f99fb04b51b", "packages": [ { "name": "adhocore/jwt", @@ -4464,17 +4464,11 @@ }, { "name": "utopia-php/migration", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/migration.git", - "reference": "c5c7544d02d2418536d41050794050132f247d62" - }, + "version": "1.6.x-dev", "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/c5c7544d02d2418536d41050794050132f247d62", - "reference": "c5c7544d02d2418536d41050794050132f247d62", - "shasum": "" + "type": "path", + "url": "../utopia-migration", + "reference": "0152f6ddf33c1c9e359c32f8021691c4e7c124b5" }, "require": { "appwrite/appwrite": "19.*", @@ -4499,7 +4493,25 @@ "Utopia\\Migration\\": "src/Migration" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Migration" + } + }, + "scripts": { + "test": [ + "./vendor/bin/phpunit" + ], + "lint": [ + "./vendor/bin/pint --test" + ], + "format": [ + "./vendor/bin/pint" + ], + "check": [ + "./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G" + ] + }, "license": [ "MIT" ], @@ -4511,11 +4523,9 @@ "upf", "utopia" ], - "support": { - "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.6.1" - }, - "time": "2026-02-17T05:49:48+00:00" + "transport-options": { + "relative": true + } }, { "name": "utopia-php/mongo", @@ -8884,9 +8894,11 @@ } ], "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, + "minimum-stability": "dev", + "stability-flags": { + "utopia-php/migration": 20 + }, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": ">=8.3.0", diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 5e729af98f5..7bc6fe8d322 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -313,6 +313,8 @@ protected function generateAPIKey(Document $project): string 'files.write', 'functions.read', 'functions.write', + 'sites.read', + 'sites.write', 'tokens.read', 'tokens.write', ] diff --git a/src/Appwrite/Utopia/Response/Model/MigrationReport.php b/src/Appwrite/Utopia/Response/Model/MigrationReport.php index 56d17d3f7f9..d20c1035eae 100644 --- a/src/Appwrite/Utopia/Response/Model/MigrationReport.php +++ b/src/Appwrite/Utopia/Response/Model/MigrationReport.php @@ -53,6 +53,24 @@ public function __construct() 'default' => 0, 'example' => 20, ]) + ->addRule(Resource::TYPE_SITE, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of sites to be migrated.', + 'default' => 0, + 'example' => 5, + ]) + ->addRule(Resource::TYPE_SITE_DEPLOYMENT, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of site deployments to be migrated.', + 'default' => 0, + 'example' => 5, + ]) + ->addRule(Resource::TYPE_SITE_VARIABLE, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of site variables to be migrated.', + 'default' => 0, + 'example' => 10, + ]) ->addRule('size', [ 'type' => self::TYPE_INTEGER, 'description' => 'Size of files to be migrated in mb.', From 8a3ece5f3dd5415bb58c2abde6febe4387c67d14 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Sun, 22 Feb 2026 20:53:18 +0000 Subject: [PATCH 2/3] added site migration E2E test --- .../Services/Migrations/MigrationsBase.php | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index a919974b291..c2424ca13a1 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -7,6 +7,7 @@ use Tests\E2E\General\UsageTest; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Services\Functions\FunctionsBase; +use Utopia\Console; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -897,6 +898,158 @@ public function testAppwriteMigrationFunction(): void ]); } + /** + * Sites + */ + public function testAppwriteMigrationSite(): void + { + $site = $this->client->call(Client::METHOD_POST, '/sites', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'siteId' => ID::unique(), + 'name' => 'Test Site', + 'framework' => 'other', + 'buildRuntime' => 'node-22', + 'adapter' => 'static', + 'outputDirectory' => './', + ]); + + $this->assertEquals(201, $site['headers']['status-code'], 'Create site failed: ' . json_encode($site['body'], JSON_PRETTY_PRINT)); + $this->assertNotEmpty($site['body']['$id']); + + $siteId = $site['body']['$id']; + + // Create deployment + $deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'code' => $this->packageSite('static'), + 'activate' => true, + ]); + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + + $deploymentId = $deployment['body']['$id']; + + // Wait for deployment to be ready + $this->assertEventually(function () use ($siteId, $deploymentId) { + $response = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments/' . $deploymentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('ready', $response['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($response['body'], JSON_PRETTY_PRINT)); + }, 300000, 500); + + // Create environment variable + $variable = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'key' => 'TEST_VAR', + 'value' => 'test_value', + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + + // Perform migration + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_SITE, + Resource::TYPE_SITE_DEPLOYMENT, + Resource::TYPE_SITE_VARIABLE, + ], + 'endpoint' => $this->webEndpoint, + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_SITE, Resource::TYPE_SITE_DEPLOYMENT, Resource::TYPE_SITE_VARIABLE], $result['resources']); + + foreach ([Resource::TYPE_SITE, Resource::TYPE_SITE_DEPLOYMENT, Resource::TYPE_SITE_VARIABLE] as $resource) { + $this->assertArrayHasKey($resource, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][$resource]['error']); + $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); + $this->assertEquals(1, $result['statusCounters'][$resource]['success']); + $this->assertEquals(0, $result['statusCounters'][$resource]['processing']); + $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); + } + + // Verify site in destination + $response = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertEquals($siteId, $response['body']['$id']); + $this->assertEquals('Test Site', $response['body']['name']); + $this->assertEquals('node-22', $response['body']['buildRuntime']); + $this->assertEquals('other', $response['body']['framework']); + $this->assertEquals('static', $response['body']['adapter']); + + // Verify deployment in destination + $this->assertEventually(function () use ($siteId) { + $deployments = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/deployments', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $deployments['headers']['status-code']); + $this->assertNotEmpty($deployments['body']); + $this->assertEquals(1, $deployments['body']['total']); + $this->assertEquals('ready', $deployments['body']['deployments'][0]['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployments['body']['deployments'][0], JSON_PRETTY_PRINT)); + }, 100000, 500); + + // Verify variable in destination + $variables = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $variables['headers']['status-code']); + $this->assertEquals(1, $variables['body']['total']); + $this->assertEquals('TEST_VAR', $variables['body']['variables'][0]['key']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + } + + private function packageSite(string $site): CURLFile + { + $stdout = ''; + $stderr = ''; + $folderPath = realpath(__DIR__ . '/../../../resources/sites') . "/$site"; + $tarPath = "$folderPath/code.tar.gz"; + + Console::execute("cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz .", '', $stdout, $stderr); + + return new CURLFile($tarPath, 'application/x-gzip', \basename($tarPath)); + } + /** * Import documents from a CSV file. */ From 51f9f1a235322b5909a6488544f5c763b879e045 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Sun, 22 Feb 2026 23:04:34 +0000 Subject: [PATCH 3/3] added messaging migration support and E2E test --- composer.json | 12 +- composer.lock | 26 ++- src/Appwrite/Platform/Workers/Migrations.php | 10 + .../Utopia/Response/Model/MigrationReport.php | 24 +++ .../Services/Migrations/MigrationsBase.php | 204 ++++++++++++++++++ 5 files changed, 258 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 0c8fc869be3..eacfef9f286 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.20.*", - "utopia-php/migration": "1.6.x-dev", + "utopia-php/migration": "dev-feat-message-migration", "utopia-php/platform": "0.7.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", @@ -112,15 +112,9 @@ }, "repositories": [ { - "type": "path", - "url": "../utopia-migration", - "options": { - "versions": { - "utopia-php/migration": "1.6.x-dev" - } - } + "type": "vcs", + "url": "https://github.com/utopia-php/migration.git" } ], - "minimum-stability": "dev", "prefer-stable": true } diff --git a/composer.lock b/composer.lock index 031bf10a1b7..f613448607f 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": "9e05c1a1869d52d4e7679f99fb04b51b", + "content-hash": "95fd9283a2ec896710a360bf90b91757", "packages": [ { "name": "adhocore/jwt", @@ -4464,11 +4464,17 @@ }, { "name": "utopia-php/migration", - "version": "1.6.x-dev", + "version": "dev-feat-message-migration", + "source": { + "type": "git", + "url": "https://github.com/utopia-php/migration.git", + "reference": "69c3eb0f2ebe256863ea394692279665b84b9e10" + }, "dist": { - "type": "path", - "url": "../utopia-migration", - "reference": "0152f6ddf33c1c9e359c32f8021691c4e7c124b5" + "type": "zip", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/69c3eb0f2ebe256863ea394692279665b84b9e10", + "reference": "69c3eb0f2ebe256863ea394692279665b84b9e10", + "shasum": "" }, "require": { "appwrite/appwrite": "19.*", @@ -4523,9 +4529,11 @@ "upf", "utopia" ], - "transport-options": { - "relative": true - } + "support": { + "source": "https://github.com/utopia-php/migration/tree/feat-message-migration", + "issues": "https://github.com/utopia-php/migration/issues" + }, + "time": "2026-02-20T08:14:06+00:00" }, { "name": "utopia-php/mongo", @@ -8894,7 +8902,7 @@ } ], "aliases": [], - "minimum-stability": "dev", + "minimum-stability": "stable", "stability-flags": { "utopia-php/migration": 20 }, diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 7bc6fe8d322..9c1fe928c7f 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -317,6 +317,16 @@ protected function generateAPIKey(Document $project): string 'sites.write', 'tokens.read', 'tokens.write', + 'providers.read', + 'providers.write', + 'topics.read', + 'topics.write', + 'subscribers.read', + 'subscribers.write', + 'messages.read', + 'messages.write', + 'targets.read', + 'targets.write', ] ]); diff --git a/src/Appwrite/Utopia/Response/Model/MigrationReport.php b/src/Appwrite/Utopia/Response/Model/MigrationReport.php index d20c1035eae..8c0c2a25b8c 100644 --- a/src/Appwrite/Utopia/Response/Model/MigrationReport.php +++ b/src/Appwrite/Utopia/Response/Model/MigrationReport.php @@ -71,6 +71,30 @@ public function __construct() 'default' => 0, 'example' => 10, ]) + ->addRule(Resource::TYPE_PROVIDER, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of messaging providers to be migrated.', + 'default' => 0, + 'example' => 5, + ]) + ->addRule(Resource::TYPE_TOPIC, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of messaging topics to be migrated.', + 'default' => 0, + 'example' => 10, + ]) + ->addRule(Resource::TYPE_SUBSCRIBER, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of messaging subscribers to be migrated.', + 'default' => 0, + 'example' => 50, + ]) + ->addRule(Resource::TYPE_MESSAGE, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of messages to be migrated.', + 'default' => 0, + 'example' => 100, + ]) ->addRule('size', [ 'type' => self::TYPE_INTEGER, 'description' => 'Size of files to be migrated in mb.', diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index c2424ca13a1..a768ba5fbeb 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -1038,6 +1038,210 @@ public function testAppwriteMigrationSite(): void ]); } + /** + * Messaging + */ + public function testAppwriteMigrationMessaging(): void + { + // Create a sendgrid email provider on source + $provider = $this->client->call(Client::METHOD_POST, '/messaging/providers/sendgrid', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'providerId' => ID::unique(), + 'name' => 'Test SendGrid Provider', + 'apiKey' => 'test-api-key', + 'from' => 'test@example.com', + ]); + + $this->assertEquals(201, $provider['headers']['status-code']); + $this->assertNotEmpty($provider['body']['$id']); + $providerId = $provider['body']['$id']; + + // Create a topic on source + $topic = $this->client->call(Client::METHOD_POST, '/messaging/topics', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'topicId' => ID::unique(), + 'name' => 'Test Topic', + ]); + + $this->assertEquals(201, $topic['headers']['status-code']); + $this->assertNotEmpty($topic['body']['$id']); + $topicId = $topic['body']['$id']; + + // Create a user on source (needed for subscriber target) + $user = $this->client->call(Client::METHOD_POST, '/users', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'userId' => ID::unique(), + 'email' => 'messaging-test@example.com', + 'password' => 'password', + 'name' => 'Messaging Test User', + ]); + + $this->assertEquals(201, $user['headers']['status-code']); + $this->assertNotEmpty($user['body']['$id']); + $userId = $user['body']['$id']; + $targetId = $user['body']['targets'][0]['$id']; + + // Create a subscriber on source + $subscriber = $this->client->call(Client::METHOD_POST, '/messaging/topics/' . $topicId . '/subscribers', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'subscriberId' => ID::unique(), + 'targetId' => $targetId, + ]); + + $this->assertEquals(201, $subscriber['headers']['status-code']); + $this->assertNotEmpty($subscriber['body']['$id']); + $subscriberId = $subscriber['body']['$id']; + + // Create a draft email message on source + $message = $this->client->call(Client::METHOD_POST, '/messaging/messages/email', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'messageId' => ID::unique(), + 'topics' => [$topicId], + 'subject' => 'Test Migration Message', + 'content' => 'This is a test migration message.', + 'draft' => true, + ]); + + $this->assertEquals(201, $message['headers']['status-code']); + $this->assertNotEmpty($message['body']['$id']); + $messageId = $message['body']['$id']; + + // Perform migration (include users so targets exist on destination for subscribers) + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + Resource::TYPE_PROVIDER, + Resource::TYPE_TOPIC, + Resource::TYPE_SUBSCRIBER, + Resource::TYPE_MESSAGE, + ], + 'endpoint' => $this->webEndpoint, + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([ + Resource::TYPE_USER, + Resource::TYPE_PROVIDER, + Resource::TYPE_TOPIC, + Resource::TYPE_SUBSCRIBER, + Resource::TYPE_MESSAGE, + ], $result['resources']); + + foreach ([Resource::TYPE_USER, Resource::TYPE_PROVIDER, Resource::TYPE_TOPIC, Resource::TYPE_MESSAGE] as $resource) { + $this->assertArrayHasKey($resource, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][$resource]['error']); + $this->assertEquals(0, $result['statusCounters'][$resource]['pending']); + $this->assertEquals(1, $result['statusCounters'][$resource]['success']); + $this->assertEquals(0, $result['statusCounters'][$resource]['processing']); + $this->assertEquals(0, $result['statusCounters'][$resource]['warning']); + } + + // Verify provider on destination + $response = $this->client->call(Client::METHOD_GET, '/messaging/providers/' . $providerId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($providerId, $response['body']['$id']); + $this->assertEquals('Test SendGrid Provider', $response['body']['name']); + + // Verify topic on destination + $response = $this->client->call(Client::METHOD_GET, '/messaging/topics/' . $topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($topicId, $response['body']['$id']); + $this->assertEquals('Test Topic', $response['body']['name']); + + // Verify message on destination + $response = $this->client->call(Client::METHOD_GET, '/messaging/messages/' . $messageId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($messageId, $response['body']['$id']); + + // Cleanup source + $this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId . '/subscribers/' . $subscriberId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + // Cleanup destination + $this->client->call(Client::METHOD_DELETE, '/messaging/messages/' . $messageId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/messaging/topics/' . $topicId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/messaging/providers/' . $providerId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]); + } + private function packageSite(string $site): CURLFile { $stdout = '';