diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c53b03b52..29aa9b0581 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -125,6 +125,7 @@ jobs: Webhooks, VCS, Messaging, + Migrations ] steps: diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 45970cd29d..f764c4c3ea 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -332,7 +332,7 @@ App::post('/v1/account') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -395,7 +395,7 @@ App::post('/v1/account') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -834,7 +834,7 @@ App::post('/v1/account/sessions/email') Query::equal('email', [$email]), ]); - if (!$profile || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { + if ($profile->isEmpty() || empty($profile->getAttribute('passwordUpdate')) || !Auth::passwordVerify($password, $profile->getAttribute('password'), $profile->getAttribute('hash'), $profile->getAttribute('hashOptions'))) { throw new Exception(Exception::USER_INVALID_CREDENTIALS); } @@ -1374,7 +1374,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if (!empty($identityWithMatchingEmail)) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_ALREADY_EXISTS); } @@ -1405,7 +1405,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if ($session !== false && !$session->isEmpty()) { + if (!$session->isEmpty()) { $user->setAttributes($dbForProject->getDocument('users', $session->getAttribute('userId'))->getArrayCopy()); } } @@ -1423,7 +1423,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $userWithEmail = $dbForProject->findOne('users', [ Query::equal('email', [$email]), ]); - if ($userWithEmail !== false && !$userWithEmail->isEmpty()) { + if (!$userWithEmail->isEmpty()) { $user->setAttributes($userWithEmail->getArrayCopy()); } @@ -1434,7 +1434,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('providerUid', [$oauth2ID]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { $user = $dbForProject->getDocument('users', $identity->getAttribute('userId')); } } @@ -1454,7 +1454,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -1517,7 +1517,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') Query::equal('provider', [$provider]), Query::equal('providerUid', [$oauth2ID]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { // Before creating the identity, check if the email is already associated with another user $userId = $user->getId(); @@ -1801,7 +1801,7 @@ App::post('/v1/account/tokens/magic-url') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -1818,7 +1818,7 @@ App::post('/v1/account/tokens/magic-url') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } @@ -2042,7 +2042,7 @@ App::post('/v1/account/tokens/email') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('email', [$email])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2059,7 +2059,7 @@ App::post('/v1/account/tokens/email') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -2329,7 +2329,7 @@ App::post('/v1/account/tokens/phone') $isAppUser = Auth::isAppUser($roles); $result = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if ($result !== false && !$result->isEmpty()) { + if (!$result->isEmpty()) { $user->setAttributes($result->getArrayCopy()); } else { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -2386,7 +2386,7 @@ App::post('/v1/account/tokens/phone') $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget]); + $user->setAttribute('targets', [...$user->getAttribute('targets', []), $existingTarget->isEmpty() ? false : $existingTarget]); } $dbForProject->purgeCachedDocument('users', $user->getId()); } @@ -2753,7 +2753,7 @@ App::patch('/v1/account/email') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */ } @@ -2774,7 +2774,7 @@ App::patch('/v1/account/email') Query::equal('identifier', [$email]), ])); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -2840,7 +2840,7 @@ App::patch('/v1/account/phone') Query::equal('identifier', [$phone]), ])); - if ($target instanceof Document && !$target->isEmpty()) { + if (!$target->isEmpty()) { throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); } @@ -2999,7 +2999,7 @@ App::post('/v1/account/recovery') Query::equal('email', [$email]), ]); - if (!$profile) { + if ($profile->isEmpty()) { throw new Exception(Exception::USER_NOT_FOUND); } diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 7e0fefa544..6a2ba3540c 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2327,7 +2327,7 @@ App::delete('/v1/functions/:functionId/executions/:executionId') Query::equal('active', [true]), ]); - if ($schedule && !$schedule->isEmpty()) { + if (!$schedule->isEmpty()) { $schedule ->setAttribute('resourceUpdatedAt', DateTime::now()) ->setAttribute('active', false); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index a4880cef86..bebb6ebaea 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -121,7 +121,7 @@ App::post('/v1/migrations/firebase/oauth') Query::equal('provider', ['firebase']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -576,7 +576,7 @@ App::get('/v1/migrations/firebase/report/oauth') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -751,13 +751,13 @@ App::get('/v1/migrations/firebase/redirect') Query::equal('providerEmail', [$email]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } } - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { $identity = $identity ->setAttribute('providerAccessToken', $accessToken) ->setAttribute('providerRefreshToken', $refreshToken) @@ -820,7 +820,7 @@ App::get('/v1/migrations/firebase/projects') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } @@ -900,7 +900,7 @@ App::get('/v1/migrations/firebase/deauthorize') Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND } diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 58ac59484f..7867b42aab 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1088,7 +1088,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1131,7 +1131,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1181,7 +1181,7 @@ App::patch('/v1/projects/:projectId/webhooks/:webhookId/signature') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1219,7 +1219,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($webhook === false || $webhook->isEmpty()) { + if ($webhook->isEmpty()) { throw new Exception(Exception::WEBHOOK_NOT_FOUND); } @@ -1341,7 +1341,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1378,7 +1378,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1420,7 +1420,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($key === false || $key->isEmpty()) { + if ($key->isEmpty()) { throw new Exception(Exception::KEY_NOT_FOUND); } @@ -1578,7 +1578,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1615,7 +1615,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } @@ -1659,7 +1659,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') Query::equal('projectInternalId', [$project->getInternalId()]), ]); - if ($platform === false || $platform->isEmpty()) { + if ($platform->isEmpty()) { throw new Exception(Exception::PLATFORM_NOT_FOUND); } diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 984a9fb974..02a3ec8e9d 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -64,7 +64,7 @@ App::post('/v1/proxy/rules') Query::equal('domain', [$domain]), ]); - if ($document && !$document->isEmpty()) { + if (!$document->isEmpty()) { if ($document->getAttribute('projectId') === $project->getId()) { $resourceType = $document->getAttribute('resourceType'); $resourceId = $document->getAttribute('resourceId'); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index f9abaeeb44..0b97be0e55 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -467,17 +467,17 @@ App::post('/v1/teams/:teamId/memberships') $name = empty($name) ? $invitee->getAttribute('name', '') : $name; } elseif (!empty($email)) { $invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address - if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { + if (!$invitee->isEmpty() && !empty($phone) && $invitee->getAttribute('phone', '') !== $phone) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409); } } elseif (!empty($phone)) { $invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]); - if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') !== $email) { + if (!$invitee->isEmpty() && !empty($email) && $invitee->getAttribute('email', '') !== $email) { throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409); } } - if (empty($invitee)) { // Create new user if no user with same email found + if ($invitee->isEmpty()) { // Create new user if no user with same email found $limit = $project->getAttribute('auths', [])['limit'] ?? 0; if (!$isPrivilegedUser && !$isAppUser && $limit !== 0 && $project->getId() !== 'console') { // check users limit, console invites are allways allowed. @@ -492,7 +492,7 @@ App::post('/v1/teams/:teamId/memberships') $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 3dffded6f6..314e298d6b 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -64,7 +64,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $identityWithMatchingEmail = $dbForProject->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } } @@ -141,7 +141,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$email]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -165,7 +165,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e $existingTarget = $dbForProject->findOne('targets', [ Query::equal('identifier', [$phone]), ]); - if ($existingTarget) { + if (!$existingTarget->isEmpty()) { $user->setAttribute('targets', $existingTarget, Document::SET_TYPE_APPEND); } } @@ -1232,7 +1232,7 @@ App::patch('/v1/users/:userId/email') Query::equal('providerEmail', [$email]), Query::notEqual('userInternalId', $user->getInternalId()), ]); - if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { + if (!$identityWithMatchingEmail->isEmpty()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index e79eb67936..bbb1d9c3f8 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -110,7 +110,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId Query::orderDesc('$createdAt'), ])); - if ($latestComment !== false && !$latestComment->isEmpty()) { + if (!$latestComment->isEmpty()) { $latestCommentId = $latestComment->getAttribute('providerCommentId', ''); $comment = new Comment(); $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); @@ -371,13 +371,11 @@ App::get('/v1/vcs/github/callback') $identity = $dbForConsole->findOne('identities', [ Query::equal('providerEmail', [$email]), ]); - if ($identity !== false && !$identity->isEmpty()) { + if (!$identity->isEmpty()) { if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); } - } - if ($identity !== false && !$identity->isEmpty()) { $identity = $identity ->setAttribute('providerAccessToken', $accessToken) ->setAttribute('providerRefreshToken', $refreshToken) @@ -418,7 +416,7 @@ App::get('/v1/vcs/github/callback') Query::equal('projectInternalId', [$projectInternalId]) ]); - if ($installation === false || $installation->isEmpty()) { + if ($installation->isEmpty()) { $teamId = $project->getAttribute('teamId', ''); $installation = new Document([ @@ -726,7 +724,7 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories') Query::equal('provider', ['github']), Query::equal('userInternalId', [$user->getInternalId()]), ]); - if ($identity === false || $identity->isEmpty()) { + if ($identity->isEmpty()) { throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); } diff --git a/app/controllers/general.php b/app/controllers/general.php index 652b0b6ef3..e996342cfd 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -526,7 +526,7 @@ App::init() $mainDomain = $envDomain; } else { $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); - $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); + $mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get(); } if ($mainDomain !== $domain->get()) { @@ -536,7 +536,7 @@ App::init() Query::equal('domain', [$domain->get()]) ]); - if (!$domainDocument) { + if ($domainDocument->isEmpty()) { $domainDocument = new Document([ 'domain' => $domain->get(), 'resourceType' => 'api', diff --git a/composer.json b/composer.json index a625945b56..5fc0cc2f36 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "utopia-php/cache": "dev-feat-redis-sync as 0.10.2", "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", - "utopia-php/database": "0.53.8", + "utopia-php/database": "0.53.9", "utopia-php/domains": "0.5.*", "utopia-php/dsn": "0.2.1", "utopia-php/framework": "0.33.*", diff --git a/phpunit.xml b/phpunit.xml index 90ebd4225f..4c4e55ea4e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -33,6 +33,7 @@ ./tests/e2e/Services/Storage ./tests/e2e/Services/Webhooks ./tests/e2e/Services/Messaging + ./tests/e2e/Services/Migrations ./tests/e2e/Services/Functions/FunctionsBase.php ./tests/e2e/Services/Functions/FunctionsCustomServerTest.php ./tests/e2e/Services/Functions/FunctionsCustomClientTest.php diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index ad2c93790c..7f84ace6f2 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -94,6 +94,8 @@ trait ProjectCustom 'topics.read', 'subscribers.write', 'subscribers.read', + 'migrations.write', + 'migrations.read' ], ]); diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php new file mode 100644 index 0000000000..e4c7ba7712 --- /dev/null +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -0,0 +1,895 @@ +getProject(true); + self::$project = $projectBackup; + + return self::$destinationProject; + } + + public function performMigrationSync( + array $body, + ): array { + $migration = $this->client->call(Client::METHOD_POST, '/migrations/appwrite', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ], $body); + + $this->assertEquals(202, $migration['headers']['status-code']); + $this->assertNotEmpty($migration['body']); + $this->assertNotEmpty($migration['body']['$id']); + + $attempts = 0; + while ($attempts < 5) { + $response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migration['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + if ($response['body']['status'] === 'failed') { + $this->fail('Migration failed', json_encode($response['body'], JSON_PRETTY_PRINT)); + } + + $this->assertNotEquals('failed', $response['body']['status']); + + if ($response['body']['status'] === 'completed') { + return $response['body']; + } + + if ($attempts === 4) { + $this->assertEquals('completed', $response['body']['status']); + } + + $attempts++; + sleep(5); + } + } + + /** + * Appwrite E2E Migration Tests + */ + public function testCreateAppwriteMigration() + { + $response = $this->performMigrationSync([ + 'resources' => Appwrite::getSupportedResources(), + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(Appwrite::getSupportedResources(), $response['resources']); + $this->assertEquals('Appwrite', $response['source']); + $this->assertEquals('Appwrite', $response['destination']); + $this->assertEmpty($response['statusCounters']); + } + + /** + * Auth + */ + public function testAppwriteMigrationAuthUserPassword() + { + $response = $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' => 'test@test.com', + 'password' => 'password', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('test@test.com', $response['body']['email']); + + $user = $response['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($user['email'], $response['body']['email']); + $this->assertEquals($user['password'], $response['body']['password']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationAuthUserPhone() + { + $response = $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(), + 'phone' => '+12065550100', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals('+12065550100', $response['body']['phone']); + + $user = $response['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($user['phone'], $response['body']['phone']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationAuthTeam() + { + $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' => 'test@test.com', + 'password' => 'password', + ]); + + $this->assertEquals(201, $user['headers']['status-code']); + $this->assertNotEmpty($user['body']); + $this->assertNotEmpty($user['body']['$id']); + $this->assertEquals('test@test.com', $user['body']['email']); + + $team = $this->client->call(Client::METHOD_POST, '/teams', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'teamId' => ID::unique(), + 'name' => 'Test Team', + ]); + + $this->assertEquals(201, $team['headers']['status-code']); + $this->assertNotEmpty($team['body']); + $this->assertNotEmpty($team['body']['$id']); + + $membership = $this->client->call(Client::METHOD_POST, '/teams/' . $team['body']['$id'] . '/memberships', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'teamId' => $team['body']['$id'], + 'userId' => $user['body']['$id'], + 'roles' => ['owner'], + ]); + + $this->assertEquals(201, $membership['headers']['status-code']); + $this->assertNotEmpty($membership['body']); + $this->assertNotEmpty($membership['body']['$id']); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_USER, + Resource::TYPE_TEAM, + Resource::TYPE_MEMBERSHIP, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_USER, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_USER]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_USER]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_TEAM, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_TEAM]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_TEAM]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_MEMBERSHIP, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_MEMBERSHIP]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals($team['body']['name'], $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, '/teams/' . $team['body']['$id'] . '/memberships', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $membership = $response['body']['memberships'][0]; + + $this->assertEquals($user['body']['$id'], $membership['userId']); + $this->assertEquals($team['body']['$id'], $membership['teamId']); + $this->assertEquals(['owner'], $membership['roles']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/users/' . $user['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/teams/' . $team['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Databases + */ + public function testAppwriteMigrationDatabase() + { + $response = $this->client->call(Client::METHOD_POST, '/databases', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'databaseId' => ID::unique(), + 'name' => 'Test Database' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $databaseId = $response['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_DATABASE, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DATABASE]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DATABASE]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($databaseId, $response['body']['$id']); + $this->assertEquals('Test Database', $response['body']['name']); + + // Cleanup on destination + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + return [ + 'databaseId' => $databaseId, + ]; + } + + /** + * @depends testAppwriteMigrationDatabase + */ + public function testAppwriteMigrationDatabasesCollection(array $data) + { + $databaseId = $data['databaseId']; + + $collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'collectionId' => ID::unique(), + 'name' => 'Test Collection', + ]); + + $this->assertEquals(201, $collection['headers']['status-code']); + + $collectionId = $collection['body']['$id']; + + // Create Attribute + $response = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'key' => 'name', + 'size' => 100, + 'encrypt' => false, + 'required' => true + ]); + + $this->assertEquals(202, $response['headers']['status-code']); + + // Wait for attribute to be ready + $this->assertEventually(function () use ($databaseId, $collectionId) { + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + '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('available', $response['body']['status']); + }, 5000, 500); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE], $result['resources']); + + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] 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']); + } + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals($collectionId, $response['body']['$id']); + $this->assertEquals('Test Collection', $response['body']['name']); + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/name', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals('name', $response['body']['key']); + $this->assertEquals(100, $response['body']['size']); + $this->assertEquals(true, $response['body']['required']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + return [ + 'databaseId' => $databaseId, + 'collectionId' => $collectionId, + ]; + } + + /** + * @depends testAppwriteMigrationDatabasesCollection + */ + public function testAppwriteMigrationDatabasesDocument(array $data) + { + $databaseId = $data['databaseId']; + $collectionId = $data['collectionId']; + + $document = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'documentId' => ID::unique(), + 'data' => [ + 'name' => 'Test Document', + ] + ]); + + $this->assertEquals(201, $document['headers']['status-code']); + $this->assertNotEmpty($document['body']); + $this->assertNotEmpty($document['body']['$id']); + + $documentId = $document['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_DATABASE, + Resource::TYPE_COLLECTION, + Resource::TYPE_ATTRIBUTE, + Resource::TYPE_DOCUMENT, + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT], $result['resources']); + + //TODO: Add TYPE_DOCUMENT to the migration status counters once pending issue is resolved + foreach ([Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_ATTRIBUTE] 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']); + } + + $response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents/' . $documentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + + $this->assertEquals($documentId, $response['body']['$id']); + $this->assertEquals('Test Document', $response['body']['name']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Storage + */ + public function testAppwriteMigrationStorageBucket() + { + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + 'maximumFileSize' => 1000000, + 'allowedFileExtensions' => ['pdf'], + 'compression' => 'gzip', + 'encryption' => false, + 'antivirus' => false + ]); + + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']); + $this->assertNotEmpty($bucket['body']['$id']); + $this->assertEquals('Test Bucket', $bucket['body']['name']); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_BUCKET + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_BUCKET], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_BUCKET, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_BUCKET]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($bucket['body']['$id'], $response['body']['$id']); + $this->assertEquals($bucket['body']['name'], $response['body']['name']); + $this->assertEquals($bucket['body']['$permissions'], $response['body']['$permissions']); + $this->assertEquals($bucket['body']['maximumFileSize'], $response['body']['maximumFileSize']); + $this->assertEquals($bucket['body']['allowedFileExtensions'], $response['body']['allowedFileExtensions']); + $this->assertEquals($bucket['body']['compression'], $response['body']['compression']); + $this->assertEquals($bucket['body']['encryption'], $response['body']['encryption']); + $this->assertEquals($bucket['body']['antivirus'], $response['body']['antivirus']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucket['body']['$id'], [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + } + + public function testAppwriteMigrationStorageFiles() + { + $bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'bucketId' => ID::unique(), + 'name' => 'Test Bucket', + 'fileSecurity' => true, + 'maximumFileSize' => 2000000, //2MB + 'allowedFileExtensions' => ['jpg', 'png', 'jfif'], + 'permissions' => [ + Permission::read(Role::any()), + Permission::create(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + $this->assertEquals(201, $bucket['headers']['status-code']); + $this->assertNotEmpty($bucket['body']['$id']); + + $bucketId = $bucket['body']['$id']; + + $file = $this->client->call(Client::METHOD_POST, '/storage/buckets/' . $bucketId . '/files', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], [ + 'fileId' => ID::unique(), + 'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'), + 'permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::any()), + Permission::delete(Role::any()), + ], + ]); + + $this->assertEquals(201, $file['headers']['status-code']); + $this->assertNotEmpty($file['body']['$id']); + + $fileId = $file['body']['$id']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_BUCKET, + Resource::TYPE_FILE + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_BUCKET, Resource::TYPE_FILE], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_BUCKET, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_BUCKET]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_BUCKET]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_FILE, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_FILE]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FILE]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($fileId, $response['body']['$id']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } + + /** + * Functions + */ + public function testAppwriteMigrationFunction() + { + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test', + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php' + ]); + + $deploymentId = $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.php', + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_FUNCTION, + Resource::TYPE_DEPLOYMENT + ], + 'endpoint' => 'http://localhost/v1', + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_FUNCTION, Resource::TYPE_DEPLOYMENT], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_FUNCTION, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_FUNCTION]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_FUNCTION]['warning']); + + $this->assertArrayHasKey(Resource::TYPE_DEPLOYMENT, $result['statusCounters']); + + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['pending']); + $this->assertEquals(1, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_DEPLOYMENT]['warning']); + + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + + $this->assertEquals($functionId, $response['body']['$id']); + $this->assertEquals('Test', $response['body']['name']); + $this->assertEquals('php-8.0', $response['body']['runtime']); + $this->assertEquals('index.php', $response['body']['entrypoint']); + + + $this->assertEventually(function () use ($functionId) { + $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['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)); + }, 50000, 500); + + // Attempt execution + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ], [ + 'body' => 'test' + ]); + + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertStringContainsString('body-is-test', $execution['body']['logs']); + + // Cleanup + $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDesintationProject()['$id'], + 'x-appwrite-key' => $this->getDesintationProject()['apiKey'], + ]); + } +} diff --git a/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php b/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php new file mode 100644 index 0000000000..2167ef9338 --- /dev/null +++ b/tests/e2e/Services/Migrations/MigrationsConsoleClientTest.php @@ -0,0 +1,12 @@ +