diff --git a/.env b/.env index 9cccf5ee7e..b4341ba2ca 100644 --- a/.env +++ b/.env @@ -9,7 +9,8 @@ _APP_CONSOLE_WHITELIST_IPS= _APP_CONSOLE_COUNTRIES_DENYLIST=AQ _APP_CONSOLE_HOSTNAMES=localhost,appwrite.io,*.appwrite.io _APP_SYSTEM_EMAIL_NAME=Appwrite -_APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io +_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io +_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io _APP_EMAIL_SECURITY=security@appwrite.io _APP_EMAIL_CERTIFICATES=certificates@appwrite.io _APP_SYSTEM_RESPONSE_FORMAT= diff --git a/app/config/collections.php b/app/config/collections.php index b8667d0b8d..e4dadcf0fc 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -5772,7 +5772,7 @@ $bucketCollections = [ '$id' => ID::custom('metadata'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 16384, // https://tools.ietf.org/html/rfc4288#section-4.2 + 'size' => 75000, // https://tools.ietf.org/html/rfc4288#section-4.2 'signed' => true, 'required' => false, 'default' => null, diff --git a/app/config/locale/templates.php b/app/config/locale/templates.php index e013c3ccc9..6aa376678a 100644 --- a/app/config/locale/templates.php +++ b/app/config/locale/templates.php @@ -7,7 +7,8 @@ return [ 'recovery', 'invitation', 'mfaChallenge', - 'sessionAlert' + 'sessionAlert', + 'otpSession' ], 'sms' => [ 'verification', diff --git a/app/config/variables.php b/app/config/variables.php index eced52d80a..70d0bcee1c 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -144,8 +144,17 @@ return [ ], [ 'name' => '_APP_SYSTEM_EMAIL_ADDRESS', - 'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'team@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.', + 'description' => 'This is the sender email address that will appear on email messages sent to developers from the Appwrite console. The default value is \'noreply@appwrite.io\'. You should choose an email address that is allowed to be used from your SMTP server to avoid the server email ending in the users\' SPAM folders.', 'introduction' => '0.7.0', + 'default' => 'noreply@appwrite.io', + 'required' => false, + 'question' => '', + 'filter' => '' + ], + [ + 'name' => '_APP_SYSTEM_TEAM_EMAIL', + 'description' => 'This is the sender email address that will appear in the generated specs. The default value is \'team@appwrite.io\'.', + 'introduction' => '1.6.0', 'default' => 'team@appwrite.io', 'required' => false, 'question' => '', diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 431744ac17..38b4721f34 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -49,6 +49,7 @@ use Utopia\Validator\AnyOf; use Utopia\Validator\ArrayList; use Utopia\Validator\Assoc; use Utopia\Validator\Boolean; +use Utopia\Validator\Nullable; use Utopia\Validator\Range; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -690,7 +691,7 @@ App::put('/v1/functions/:functionId') ->param('commands', '', new Text(8192, 0), 'Build Commands.', true) ->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true) - ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true) + ->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true) ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true) @@ -702,7 +703,7 @@ App::put('/v1/functions/:functionId') ->inject('queueForBuilds') ->inject('dbForConsole') ->inject('gitHub') - ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) { + ->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) { // TODO: If only branch changes, re-deploy $function = $dbForProject->getDocument('functions', $functionId); @@ -740,8 +741,8 @@ App::put('/v1/functions/:functionId') $isConnected = !empty($function->getAttribute('providerRepositoryId', '')); - // Git disconnect logic - if ($isConnected && empty($providerRepositoryId)) { + // Git disconnect logic. Disconnecting only when providerRepositoryId is empty, allowing for continue updates without disconnecting git + if ($isConnected && ($providerRepositoryId !== null && empty($providerRepositoryId))) { $repositories = $dbForConsole->find('repositories', [ Query::equal('projectInternalId', [$project->getInternalId()]), Query::equal('resourceInternalId', [$function->getInternalId()]), diff --git a/app/init.php b/app/init.php index 892d9209a8..b566de9518 100644 --- a/app/init.php +++ b/app/init.php @@ -769,13 +769,22 @@ $register->set('logger', function () { throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled"); } - $adapter = match ($providerName) { - 'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']), - 'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']), - 'raygun' => new Raygun($providerConfig['key']), - 'appsignal' => new AppSignal($providerConfig['key']), - default => throw new Exception('Provider "' . $providerName . '" not supported.') - }; + try { + $adapter = match ($providerName) { + 'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']), + 'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']), + 'raygun' => new Raygun($providerConfig['key']), + 'appsignal' => new AppSignal($providerConfig['key']), + default => null + }; + } catch (Throwable $th) { + $adapter = null; + } + + if($adapter === null) { + Console::error("Logging provider not supported. Logging is disabled"); + return; + } return new Logger($adapter); }); diff --git a/composer.json b/composer.json index 3bb2816028..93b9879a10 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "utopia-php/fetch": "0.2.*", "utopia-php/image": "0.6.*", "utopia-php/locale": "0.4.*", - "utopia-php/logger": "0.5.*", + "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.12.*", "utopia-php/migration": "0.4.*", "utopia-php/orchestration": "0.9.*", diff --git a/composer.lock b/composer.lock index 76b1579a87..8fc3445b60 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fc07cbc344782534962fd7bf0769f7b9", + "content-hash": "349cdb39bce652b164706de8b399b911", "packages": [ { "name": "adhocore/jwt", @@ -1721,16 +1721,16 @@ }, { "name": "utopia-php/database", - "version": "0.50.2", + "version": "0.50.3", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "c712d1f6c8ec37886a7a1ad4d60a8cd75dec00aa" + "reference": "4287e6625c7273411c7322abd151c4285ee7b50f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/c712d1f6c8ec37886a7a1ad4d60a8cd75dec00aa", - "reference": "c712d1f6c8ec37886a7a1ad4d60a8cd75dec00aa", + "url": "https://api.github.com/repos/utopia-php/database/zipball/4287e6625c7273411c7322abd151c4285ee7b50f", + "reference": "4287e6625c7273411c7322abd151c4285ee7b50f", "shasum": "" }, "require": { @@ -1771,9 +1771,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.50.2" + "source": "https://github.com/utopia-php/database/tree/0.50.3" }, - "time": "2024-07-31T10:12:19+00:00" + "time": "2024-08-08T01:40:54+00:00" }, { "name": "utopia-php/domains", @@ -2067,16 +2067,16 @@ }, { "name": "utopia-php/logger", - "version": "0.5.2", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/logger.git", - "reference": "c6dfdb672e41364c309b0c30dc03bc6d45446dba" + "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/logger/zipball/c6dfdb672e41364c309b0c30dc03bc6d45446dba", - "reference": "c6dfdb672e41364c309b0c30dc03bc6d45446dba", + "url": "https://api.github.com/repos/utopia-php/logger/zipball/a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", + "reference": "a2d1daeeb8f61fdec6d851950d9a021a3d05c9f9", "shasum": "" }, "require": { @@ -2115,9 +2115,9 @@ ], "support": { "issues": "https://github.com/utopia-php/logger/issues", - "source": "https://github.com/utopia-php/logger/tree/0.5.2" + "source": "https://github.com/utopia-php/logger/tree/0.6.0" }, - "time": "2024-05-17T09:32:59+00:00" + "time": "2024-05-23T13:37:54+00:00" }, { "name": "utopia-php/messaging", @@ -2990,16 +2990,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.5", + "version": "0.39.6", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "40d0f66f2f85be74049ad710b46203aa151f53fd" + "reference": "00e6f9e77ea380d8ab3138a36548b353077e4061" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/40d0f66f2f85be74049ad710b46203aa151f53fd", - "reference": "40d0f66f2f85be74049ad710b46203aa151f53fd", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/00e6f9e77ea380d8ab3138a36548b353077e4061", + "reference": "00e6f9e77ea380d8ab3138a36548b353077e4061", "shasum": "" }, "require": { @@ -3035,9 +3035,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.5" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.6" }, - "time": "2024-08-06T00:51:40+00:00" + "time": "2024-08-08T12:44:28+00:00" }, { "name": "doctrine/deprecations", diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 7dd2912234..af2d86a2ba 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -34,6 +34,11 @@ class V21 extends Migration Console::info('Migrating Collections'); $this->migrateCollections(); + if ($this->project->getInternalId() !== 'console') { + Console::info('Migrating Buckets'); + $this->migrateBuckets(); + } + Console::info('Migrating Documents'); $this->forEachDocument([$this, 'fixDocument']); } @@ -177,4 +182,23 @@ class V21 extends Migration return $document; } + + /** + * Migrating Buckets. + * + * @return void + */ + private function migrateBuckets() + { + foreach ($this->documentsIterator('buckets') as $bucket) { + $bucketId = 'bucket_' . $bucket['$internalId']; + + try { + $this->projectDB->updateAttribute($bucketId, 'metadata', size: 75000); + $this->projectDB->purgeCachedCollection($bucketId); + } catch (\Throwable $th) { + Console::warning("'bucketId' from {$bucketId}: {$th->getMessage()}"); + } + } + } } diff --git a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php index 2fdbd98da3..c5f9b40d15 100644 --- a/src/Appwrite/Platform/Tasks/ScheduleExecutions.php +++ b/src/Appwrite/Platform/Tasks/ScheduleExecutions.php @@ -3,6 +3,7 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Event\Func; +use Swoole\Coroutine as Co; use Utopia\Database\Database; use Utopia\Pools\Group; @@ -26,6 +27,7 @@ class ScheduleExecutions extends ScheduleBase $queue = $pools->get('queue')->pop(); $connection = $queue->getResource(); $queueForFunctions = new Func($connection); + $intervalEnd = (new \DateTime())->modify('+' . self::ENQUEUE_TIMER . ' seconds'); foreach ($this->schedules as $schedule) { if (!$schedule['active']) { @@ -38,25 +40,30 @@ class ScheduleExecutions extends ScheduleBase continue; } - $now = new \DateTime(); $scheduledAt = new \DateTime($schedule['schedule']); - - if ($scheduledAt > $now) { + if ($scheduledAt <= $intervalEnd) { continue; } - $queueForFunctions - ->setType('schedule') - // Set functionId instead of function as we don't have $dbForProject - // TODO: Refactor to use function instead of functionId - ->setFunctionId($schedule['resource']['functionId']) - ->setExecution($schedule['resource']) - ->setMethod($schedule['data']['method'] ?? 'POST') - ->setPath($schedule['data']['path'] ?? '/') - ->setHeaders($schedule['data']['headers'] ?? []) - ->setBody($schedule['data']['body'] ?? '') - ->setProject($schedule['project']) - ->trigger(); + $delay = $scheduledAt->getTimestamp() - (new \DateTime())->getTimestamp(); + + + \go(function () use ($queueForFunctions, $schedule, $delay) { + Co::sleep($delay); + + $queueForFunctions + ->setType('schedule') + // Set functionId instead of function as we don't have $dbForProject + // TODO: Refactor to use function instead of functionId + ->setFunctionId($schedule['resource']['functionId']) + ->setExecution($schedule['resource']) + ->setMethod($schedule['data']['method'] ?? 'POST') + ->setPath($schedule['data']['path'] ?? '/') + ->setHeaders($schedule['data']['headers'] ?? []) + ->setBody($schedule['data']['body'] ?? '') + ->setProject($schedule['project']) + ->trigger(); + }); $dbForConsole->deleteDocument( 'schedules', diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index 114e12ac85..c90d4dabd3 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -259,7 +259,7 @@ class Specs extends Action $specs = new Specification($formatInstance); $endpoint = System::getEnv('_APP_HOME', '[HOSTNAME]'); - $email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $email = System::getEnv('_APP_SYSTEM_TEAM_EMAIL', APP_EMAIL_TEAM); $formatInstance ->setParam('name', APP_NAME) diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Deployments.php b/src/Appwrite/Utopia/Database/Validator/Queries/Deployments.php index 427779efa5..42aed88ef6 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Deployments.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Deployments.php @@ -9,7 +9,9 @@ class Deployments extends Base 'buildId', 'activate', 'entrypoint', - 'commands' + 'commands', + 'type', + 'size' ]; /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 79e7a83dda..eb2a1a1df9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -9,7 +9,6 @@ use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; use Utopia\Config\Config; -use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; @@ -195,10 +194,6 @@ class FunctionsCustomClientTest extends Scope 'execute' => [Role::user($this->getUser()['$id'])->toString()], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'events' => [ - 'users.*.create', - 'users.*.delete', - ], 'timeout' => 10, ]); @@ -217,66 +212,50 @@ class FunctionsCustomClientTest extends Scope 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, true); // Schedule execution for the future \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s'); - $futureTimeIso = DateTime::formatTz($futureTime); + $futureTime = (new \DateTime())->add(new \DateInterval('PT10S')); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'async' => true, - 'scheduledAt' => $futureTime, + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), 'path' => '/custom', - 'method' => 'GET', - 'body' => 'hello', - 'headers' => [ - 'content-type' => 'application/plain', - ], + 'method' => 'GET' ]); $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); - $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); $executionId = $execution['body']['$id']; - // List executions and ensure it has schedule date - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + sleep(10); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertGreaterThan(0, \count($response['body']['executions'])); - $recentExecution = $response['body']['executions'][0]; - $this->assertEquals($executionId, $recentExecution['$id']); - $this->assertEquals($futureTimeIso, $recentExecution['scheduledAt']); + $start = \microtime(true); + while (true) { + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); - sleep(20); + if ($execution['body']['status'] === 'completed') { + break; + } - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + if (\microtime(true) - $start > 5) { + $this->fail('Execution did not complete within 5 seconds of schedule'); + } + + usleep(500000); // 0.5 seconds + } $this->assertEquals(200, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -284,7 +263,6 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('/custom', $execution['body']['requestPath']); $this->assertEquals('GET', $execution['body']['requestMethod']); $this->assertGreaterThan(0, $execution['body']['duration']); - $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); /* Test for FAILURE */ @@ -295,7 +273,7 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'async' => false, - 'scheduledAt' => $futureTime, + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), ]); $this->assertEquals(400, $execution['headers']['status-code']); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 48eec5fdcc..086b63c21c 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -732,6 +732,108 @@ class FunctionsCustomServerTest extends Scope $this->assertCount(3, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::equal('type', ['manual'])->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(3, $function['body']['total']); + + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::equal('type', ['vcs'])->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(0, $function['body']['total']); + + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::equal('type', ['invalid-string'])->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(0, $function['body']['total']); + + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::greaterThan('size', 10000)->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(1, $function['body']['total']); + + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::greaterThan('size', 0)->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(3, $function['body']['total']); + + $function = $this->client->call( + Client::METHOD_GET, + '/functions/' . $data['functionId'] . '/deployments', + array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), + [ + 'queries' => [ + Query::greaterThan('size', -100)->toString(), + ], + ] + ); + + $this->assertEquals($function['headers']['status-code'], 200); + $this->assertEquals(3, $function['body']['total']); + return $data; }