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 @@
+