From 979fcacd6cab9bb48aae251f665ad5b576c803ba Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:35:52 +0100 Subject: [PATCH] fix: abstract requests --- .../e2e/Services/Functions/FunctionsBase.php | 391 +++++---- .../Functions/FunctionsConsoleClientTest.php | 173 ++-- .../Functions/FunctionsCustomClientTest.php | 739 +++--------------- .../Functions/FunctionsCustomServerTest.php | 358 +++------ 4 files changed, 469 insertions(+), 1192 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 7f33e0b2ee..92210a3027 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Functions; use Appwrite\Tests\Async; +use CURLFile; use Tests\E2E\Client; use Utopia\CLI\Console; @@ -13,220 +14,212 @@ trait FunctionsBase protected string $stdout = ''; protected string $stderr = ''; - protected function packageCode($folder) + protected function setupFunction(mixed $params): string { + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + $functionId = $function['body']['$id']; + + $this->assertEquals($function['headers']['status-code'], 201); + return $functionId; + } + + protected function setupDeployment(string $functionId, mixed $params): string + { + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]), $params); + $deploymentId = $deployment['body']['$id']; + + $this->assertEquals($deployment['headers']['status-code'], 202); + + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals('completed', $deployment['body']['status']); + }, 200000, 500); + + return $deploymentId; + } + + protected function cleanupFunction(string $functionId): void + { + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ])); + + $this->assertEquals($function['headers']['status-code'], 204); + } + + protected function createFunction(mixed $params) + { + $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + $functionId = $function['body']['$id']; + + if (empty($variables)) { + return $function; + } + + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $function; + } + + protected function createVariable($functionId, $key, $value) + { + $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'key' => $key, + 'value' => $value + ]); + + return $variable; + } + + protected function getFunction($functionId) + { + $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $function; + } + + protected function getDeployment($functionId, $deploymentId) + { + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $deployment; + } + + protected function getExecution($functionId, $executionId) + { + $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $execution; + } + + protected function listDeployments($functionId, $params = []) + { + $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); + + return $deployments; + } + + protected function listExecutions($functionId, $params = []) + { + $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); + + return $executions; + } + + protected function packageFunction(string $folder = 'php') + { + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; + Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); + + if (!file_exists($code)) { + throw new \Exception('Failed to create code package. ' . $code . ' does not exist.'); + } + if (filesize($code) > 1024 * 1024 * 5) { + throw new \Exception('Code package is too large. Use the chunked upload method instead.'); + } + + return new CURLFile($code, 'application/x-gzip', \basename($code)); } - protected function assertDeployment($functionId, $deploymentId, $allowFailure = false) - { - $this->assertEventually(function () use ($functionId, $deploymentId, $allowFailure) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + protected function createDeployment( + string $functionId, + mixed $params, + bool $cli = false, + bool $admin = false + ) { + $authHeaders = $this->getHeaders(); + + if ($admin) { + $authHeaders = [ 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); + ]; + } + if ($cli) { + $authHeaders[] = 'x-sdk-language: cli'; + } - $status = $deployment['body']['status']; + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], - if ($allowFailure && $status === 'failed') { - $this->fail('Deployment failed: ' . json_encode($deployment['body']['buildStderr'])); - } + ], $params); - $this->assertEquals('ready', $status); - }, 180000, 1000); + return $deployment; } - // /** - // * @depends testCreateTeam - // */ - // public function testGetTeam($data):array - // { - // $id = $data['teamUid'] ?? ''; + protected function getFunctionUsage($functionId, $params) + { + $usage = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders(), $params)); - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$id, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + return $usage; + } - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Arsenal', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); + protected function getTemplate(string $templateId) + { + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/' . $templateId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); - // /** - // * Test for FAILURE - // */ + return $template; + } - // return []; - // } + protected function createExecution(string $functionId, mixed $params) + { + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); - // /** - // * @depends testCreateTeam - // */ - // public function testListTeams($data):array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); + return $execution; + } - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(3, $response['body']['teams']); + protected function deleteFunction($functionId) + { + $function = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'limit' => 2, - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(2, $response['body']['teams']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'offset' => 1, - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(2, $response['body']['teams']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'search' => 'Manchester', - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(1, $response['body']['teams']); - // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']); - - // $response = $this->client->call(Client::METHOD_GET, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'search' => 'United', - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertGreaterThan(0, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertCount(1, $response['body']['teams']); - // $this->assertEquals('Manchester United', $response['body']['teams'][0]['name']); - - // /** - // * Test for FAILURE - // */ - - // return []; - // } - - // public function testUpdateTeam():array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo' - // ]); - - // $this->assertEquals(201, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo New' - // ]); - - // $this->assertEquals(200, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo New', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // /** - // * Test for FAILURE - // */ - // $response = $this->client->call(Client::METHOD_PUT, '/teams/'.$response['body']['$id'], array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // ]); - - // $this->assertEquals(400, $response['headers']['status-code']); - - // return []; - // } - - // public function testDeleteTeam():array - // { - // /** - // * Test for SUCCESS - // */ - // $response = $this->client->call(Client::METHOD_POST, '/teams', array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders()), [ - // 'name' => 'Demo' - // ]); - - // $teamUid = $response['body']['$id']; - - // $this->assertEquals(201, $response['headers']['status-code']); - // $this->assertNotEmpty($response['body']['$id']); - // $this->assertEquals('Demo', $response['body']['name']); - // $this->assertGreaterThan(-1, $response['body']['total']); - // $this->assertIsInt($response['body']['total']); - // $this->assertIsInt($response['body']['dateCreated']); - - // $response = $this->client->call(Client::METHOD_DELETE, '/teams/'.$teamUid, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(204, $response['headers']['status-code']); - // $this->assertEmpty($response['body']); - - // /** - // * Test for FAILURE - // */ - // $response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid, array_merge([ - // 'content-type' => 'application/json', - // 'x-appwrite-project' => $this->getProject()['$id'], - // ], $this->getHeaders())); - - // $this->assertEquals(404, $response['headers']['status-code']); - - // return []; - // } + return $function; + } } diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index 8cb7f6f869..6ee5a1085d 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -13,13 +13,11 @@ class FunctionsConsoleClientTest extends Scope { use ProjectCustom; use SideConsole; + use FunctionsBase; public function testCreateFunction(): array { - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -32,87 +30,69 @@ class FunctionsConsoleClientTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function2 = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test Failure', 'execute' => ['some-random-string'], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', ]); - - $this->assertEquals(400, $response['headers']['status-code']); + $this->assertEquals(400, $function2['headers']['status-code']); return [ - 'functionId' => $function['body']['$id'] + 'functionId' => $functionId, ]; } /** * @depends testCreateFunction */ - public function testGetCollectionUsage(array $data) + public function testFunctionUsage(array $data) { - /** - * Test for FAILURE - */ - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '232h' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $response = $this->client->call(Client::METHOD_GET, '/functions/randomFunctionId/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'range' => '24h' - ]); - - $this->assertEquals(404, $response['headers']['status-code']); - /** * Test for SUCCESS */ - - $response = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/usage', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ + $usage = $this->getFunctionUsage($data['functionId'], [ 'range' => '24h' ]); + $this->assertEquals(200, $usage['headers']['status-code']); + $this->assertEquals(19, count($usage['body'])); + $this->assertEquals('24h', $usage['body']['range']); + $this->assertIsNumeric($usage['body']['deploymentsTotal']); + $this->assertIsNumeric($usage['body']['deploymentsStorageTotal']); + $this->assertIsNumeric($usage['body']['buildsTotal']); + $this->assertIsNumeric($usage['body']['buildsStorageTotal']); + $this->assertIsNumeric($usage['body']['buildsTimeTotal']); + $this->assertIsNumeric($usage['body']['buildsMbSecondsTotal']); + $this->assertIsNumeric($usage['body']['executionsTotal']); + $this->assertIsNumeric($usage['body']['executionsTimeTotal']); + $this->assertIsNumeric($usage['body']['executionsMbSecondsTotal']); + $this->assertIsArray($usage['body']['deployments']); + $this->assertIsArray($usage['body']['deploymentsStorage']); + $this->assertIsArray($usage['body']['builds']); + $this->assertIsArray($usage['body']['buildsTime']); + $this->assertIsArray($usage['body']['buildsStorage']); + $this->assertIsArray($usage['body']['buildsTime']); + $this->assertIsArray($usage['body']['buildsMbSeconds']); + $this->assertIsArray($usage['body']['executions']); + $this->assertIsArray($usage['body']['executionsTime']); + $this->assertIsArray($usage['body']['executionsMbSeconds']); - $this->assertEquals(200, $response['headers']['status-code']); - $this->assertEquals(19, count($response['body'])); - $this->assertEquals('24h', $response['body']['range']); - $this->assertIsNumeric($response['body']['deploymentsTotal']); - $this->assertIsNumeric($response['body']['deploymentsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsTotal']); - $this->assertIsNumeric($response['body']['buildsStorageTotal']); - $this->assertIsNumeric($response['body']['buildsTimeTotal']); - $this->assertIsNumeric($response['body']['buildsMbSecondsTotal']); - $this->assertIsNumeric($response['body']['executionsTotal']); - $this->assertIsNumeric($response['body']['executionsTimeTotal']); - $this->assertIsNumeric($response['body']['executionsMbSecondsTotal']); - $this->assertIsArray($response['body']['deployments']); - $this->assertIsArray($response['body']['deploymentsStorage']); - $this->assertIsArray($response['body']['builds']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsStorage']); - $this->assertIsArray($response['body']['buildsTime']); - $this->assertIsArray($response['body']['buildsMbSeconds']); - $this->assertIsArray($response['body']['executions']); - $this->assertIsArray($response['body']['executionsTime']); - $this->assertIsArray($response['body']['executionsMbSeconds']); + /** + * Test for FAILURE + */ + $usage = $this->getFunctionUsage($data['functionId'], [ + 'range' => '232h' + ]); + $this->assertEquals(400, $usage['headers']['status-code']); + + $usage = $this->getFunctionUsage('randomFunctionId', [ + 'range' => '24h' + ]); + $this->assertEquals(404, $usage['headers']['status-code']); } /** @@ -123,31 +103,40 @@ class FunctionsConsoleClientTest extends Scope /** * Test for SUCCESS */ - - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'APP_TEST', - 'value' => 'TESTINGVALUE' - ]); - - $this->assertEquals(201, $response['headers']['status-code']); - $variableId = $response['body']['$id']; + $variable = $this->createVariable( + $data['functionId'], + 'APP_TEST', + 'TESTINGVALUE' + ); + $variableId = $variable['body']['$id']; + $this->assertEquals(201, $variable['headers']['status-code']); /** * Test for FAILURE */ + // Test for duplicate key + $variable = $this->createVariable( + $data['functionId'], + 'APP_TEST', + 'ANOTHER_TESTINGVALUE' + ); + $this->assertEquals(409, $variable['headers']['status-code']); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'APP_TEST', - 'value' => 'ANOTHER_TESTINGVALUE' - ]); + // Test for invalid key + $variable = $this->createVariable( + $data['functionId'], + str_repeat("A", 256), + 'TESTINGVALUE' + ); + $this->assertEquals(400, $variable['headers']['status-code']); - $this->assertEquals(409, $response['headers']['status-code']); + // Test for invalid value + $variable = $this->createVariable( + $data['functionId'], + 'LONGKEY', + str_repeat("#", 8193) + ); + $this->assertEquals(400, $variable['headers']['status-code']); return array_merge( $data, @@ -155,28 +144,6 @@ class FunctionsConsoleClientTest extends Scope 'variableId' => $variableId ] ); - - $longKey = str_repeat("A", 256); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => $longKey, - 'value' => 'TESTINGVALUE' - ]); - - $this->assertEquals(400, $response['headers']['status-code']); - - $longValue = str_repeat("#", 8193); - $response = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'LONGKEY', - 'value' => $longValue - ]); - - $this->assertEquals(400, $response['headers']['status-code']); } /** diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index f5f15a0fc6..872249085e 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -2,16 +2,12 @@ namespace Tests\E2E\Services\Functions; -use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; -use Utopia\Config\Config; -use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; -use Utopia\Database\Query; class FunctionsCustomClientTest extends Scope { @@ -19,15 +15,12 @@ class FunctionsCustomClientTest extends Scope use ProjectCustom; use SideClient; - public function testCreate(): array + public function testCreate() { /** - * Test for SUCCESS + * Test for FAILURE */ - $response1 = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'events' => [ @@ -37,22 +30,15 @@ class FunctionsCustomClientTest extends Scope 'schedule' => '0 0 1 1 *', 'timeout' => 10, ]); - - $this->assertEquals(401, $response1['headers']['status-code']); - - return []; + $this->assertEquals(401, $function['headers']['status-code']); } - public function testCreateExecution(): array + public function testCreateExecution() { /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -62,121 +48,41 @@ class FunctionsCustomClientTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '* * * * *', // execute every minute + 'schedule' => '* * * * *', // Execute every minute 'timeout' => 10, ]); - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($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']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'async' => true, + $execution = $this->createExecution($functionId, [ + 'async' => false, ]); - $this->assertEquals(401, $execution['headers']['status-code']); - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, ]); - $this->assertEquals(202, $execution['headers']['status-code']); - $this->assertEventually(function () use ($function) { - $executions = $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'], - ]); - + $this->assertEventually(function () use ($functionId) { + $executions = $this->listExecutions($functionId); $this->assertEquals(200, $executions['headers']['status-code']); $this->assertCount(2, $executions['body']['executions']); - // Check if the scheduled execution has completed - $scheduledExecution = $executions['body']['executions'][1]; - $this->assertEquals('schedule', $scheduledExecution['trigger']); - $this->assertEquals('completed', $scheduledExecution['status']); - $this->assertEquals(200, $scheduledExecution['responseStatusCode']); - $this->assertEquals('', $scheduledExecution['responseBody']); - $this->assertNotEmpty($scheduledExecution['logs']); - $this->assertNotEmpty($scheduledExecution['errors']); - $this->assertGreaterThan(0, $scheduledExecution['duration']); - }, 120000, 2000); + $asyncExecution = $executions['body']['executions'][1]; + $this->assertEquals('schedule', $asyncExecution['trigger']); + $this->assertEquals('completed', $asyncExecution['status']); + $this->assertEquals(200, $asyncExecution['responseStatusCode']); + $this->assertEquals('', $asyncExecution['responseBody']); + $this->assertNotEmpty($asyncExecution['logs']); + $this->assertNotEmpty($asyncExecution['errors']); + $this->assertGreaterThan(0, $asyncExecution['duration']); + }, 100000, 250); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } public function testCreateScheduledExecution(): void @@ -184,11 +90,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::user($this->getUser()['$id'])->toString()], @@ -196,38 +98,20 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId, true); // Schedule execution for the future \date_default_timezone_set('UTC'); - $futureTime = (new \DateTime())->add(new \DateInterval('PT2M')); + $futureTime = (new \DateTime())->add(new \DateInterval('PT30S')); // 30 seconds from now $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), 'path' => '/custom-path', 'method' => 'PATCH', 'body' => 'custom-body', @@ -235,101 +119,60 @@ class FunctionsCustomClientTest extends Scope 'x-custom-header' => 'custom-value' ] ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); $this->assertEquals('PATCH', $execution['body']['requestMethod']); $this->assertEquals('/custom-path', $execution['body']['requestPath']); $this->assertCount(0, $execution['body']['requestHeaders']); - $executionId = $execution['body']['$id']; - - $this->assertEventually(function () use ($function, $executionId) { - $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'], - ]); - + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); - }, 150000, 2000); // Timeout after 150 seconds, wait 2 seconds between retries - - // After ensuring the execution is completed, fetch it again for assertions - $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'], - ]); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertEquals('completed', $execution['body']['status']); - $this->assertEquals('/custom-path', $execution['body']['requestPath']); - $this->assertEquals('PATCH', $execution['body']['requestMethod']); - $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); - $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); - $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); - $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); - $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); - $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); - $this->assertGreaterThan(0, $execution['body']['duration']); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals('/custom-path', $execution['body']['requestPath']); + $this->assertEquals('PATCH', $execution['body']['requestMethod']); + $this->assertStringContainsString('body-is-custom-body', $execution['body']['logs']); + $this->assertStringContainsString('custom-header-is-custom-value', $execution['body']['logs']); + $this->assertStringContainsString('method-is-patch', $execution['body']['logs']); + $this->assertStringContainsString('path-is-/custom-path', $execution['body']['logs']); + $this->assertStringContainsString('user-is-' . $this->getUser()['$id'], $execution['body']['logs']); + $this->assertStringContainsString('jwt-is-valid', $execution['body']['logs']); + $this->assertGreaterThan(0, $execution['body']['duration']); + }, 40000, 2500); /* Test for FAILURE */ - // Schedule synchronous execution - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => false, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => $futureTime->format(\DateTime::ATOM), ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution with seconds precision - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02"))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution with milliseconds precision - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, 'scheduledAt' => (new \DateTime("2100-12-08 16:12:02.255"))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); // Execution too soon - $futureTime = (new \DateTime())->add(new \DateInterval('PT1M')); - $futureTime->setTime($futureTime->format('H'), $futureTime->format('i'), 0, 0); - $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()), [ + $execution = $this->createExecution($functionId, [ 'async' => true, - 'scheduledAt' => $futureTime->format(\DateTime::ATOM), + 'scheduledAt' => (new \DateTime())->add(new \DateInterval('PT1S'))->format(\DateTime::ATOM) ]); - $this->assertEquals(400, $execution['headers']['status-code']); - // Cleanup : Delete function - $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $function['body']['$id'], [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(204, $response['headers']['status-code']); + $this->cleanupFunction($functionId, $executionId); } public function testCreateCustomExecution(): array @@ -337,14 +180,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -352,77 +188,16 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => false ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals(200, $execution['body']['responseStatusCode']); @@ -440,30 +215,20 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); $this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']); - $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']); + $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', 'async' => true ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); - $executionId = $execution['body']['$id'] ?? ''; - - $this->assertEventually(function () use ($functionId, $executionId, $projectId, $apikey) { - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); $this->assertEquals('completed', $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); - }, 30000, 1000); + }, 10000, 250); return [ 'functionId' => $functionId @@ -475,14 +240,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -495,56 +253,25 @@ class FunctionsCustomClientTest extends Scope ], 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - // Why do we have to do this? - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], []); - - $this->assertEquals(200, $function['headers']['status-code']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', [ 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, + 'x-appwrite-project' => $this->getProject()['$id'], ], [ 'data' => 'foobar', 'async' => true, ]); - $this->assertEquals(202, $execution['headers']['status-code']); } - public function testCreateExecutionNoDeployment(): array + public function testCreateExecutionNoDeployment() { - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [], @@ -553,146 +280,18 @@ class FunctionsCustomClientTest extends Scope 'timeout' => 10, ]); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], [ - 'async' => true, + $execution = $this->createExecution($functionId, [ + 'async' => true ]); - $this->assertEquals(404, $execution['headers']['status-code']); - - return []; } - /** - * @depends testCreateCustomExecution - */ - public function testListExecutions(array $data) - { - $functionId = $data['functionId']; - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ - 'data' => 'foobar' - ]); - - $this->assertEquals(201, $execution['headers']['status-code']); - - $base = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - - $this->assertEquals(200, $base['headers']['status-code']); - $this->assertCount(3, $base['body']['executions']); - $this->assertEquals('completed', $base['body']['executions'][0]['status']); - $this->assertEquals('completed', $base['body']['executions'][1]['status']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::limit(1)->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(1, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::offset(1)->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(2, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::equal('status', ['completed'])->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(3, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::equal('status', ['failed'])->toString(), - ], - ]); - - $this->assertEquals(200, $executions['headers']['status-code']); - $this->assertCount(0, $executions['body']['executions']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::cursorAfter(new Document(['$id' => $base['body']['executions'][0]['$id']]))->toString(), - ], - ]); - - $this->assertCount(2, $executions['body']['executions']); - $this->assertEquals($base['body']['executions'][1]['$id'], $executions['body']['executions'][0]['$id']); - - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'queries' => [ - Query::cursorBefore(new Document(['$id' => $base['body']['executions'][1]['$id']]))->toString(), - ], - ]); - - // Cleanup : Delete function - $response = $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->assertEquals(204, $response['headers']['status-code']); - } - - public function testSynchronousExecution(): array + public function testSynchronousExecution() { /** * Test for SUCCESS */ - - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], @@ -700,79 +299,16 @@ class FunctionsCustomClientTest extends Scope 'entrypoint' => 'index.php', 'timeout' => 10, ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - /** Create Variables */ - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey1', - 'value' => 'funcValue1', - ]); - - $variable2 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey2', - 'value' => 'funcValue2', - ]); - - $variable3 = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ - 'key' => 'funcKey3', - 'value' => 'funcValue3', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $this->assertEquals(201, $variable2['headers']['status-code']); - $this->assertEquals(201, $variable3['headers']['status-code']); - - $folder = 'php-fn'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $deploymentId = $this->setupDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('php-fn'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ]); - - $this->assertEquals(200, $function['headers']['status-code']); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'body' => 'foobar', // Testing default value, should be 'async' => false ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals('completed', $execution['body']['status']); @@ -789,67 +325,29 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); $this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']); - $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']); + $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']); // Client should never see logs and errors $this->assertEmpty($execution['body']['logs']); $this->assertEmpty($execution['body']['errors']); - // Cleanup : Delete function - $response = $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->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } - public function testNonOverrideOfHeaders(): array + public function testNonOverrideOfHeaders() { - /** - * Test for SUCCESS - */ - $projectId = $this->getProject()['$id']; - $apikey = $this->getProject()['apiKey']; - - $function = $this->client->call(Client::METHOD_POST, '/functions', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test', 'execute' => [Role::any()->toString()], 'runtime' => 'node-18.0', 'entrypoint' => 'index.js' ]); - - $functionId = $function['body']['$id'] ?? ''; - - $this->assertEquals(201, $function['headers']['status-code']); - - $folder = 'node'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $projectId, - 'x-appwrite-key' => $apikey, - ], [ + $this->setupDeployment($functionId, [ 'entrypoint' => 'index.js', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), //different tarball names intentional + 'code' => $this->packageFunction('node'), 'activate' => true ]); - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($function['body']['$id'], $deploymentId); - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -859,23 +357,13 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-user-id' => "OVERRIDDEN", 'x-appwrite-user-jwt' => "OVERRIDDEN", ]); - $output = json_decode($execution['body']['responseBody'], true); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_JWT']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_EVENT']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_TRIGGER']); $this->assertNotEquals('OVERRIDDEN', $output['APPWRITE_FUNCTION_USER_ID']); - // Cleanup : Delete function - $response = $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->assertEquals(204, $response['headers']['status-code']); - - return []; + $this->cleanupFunction($functionId); } public function testListTemplates() @@ -883,50 +371,43 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25); + // List all templates $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders())); - $this->assertEquals(200, $templates['headers']['status-code']); $this->assertGreaterThan(0, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); - - $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); - $this->assertArrayHasKey('useCases', $templates['body']['templates'][0]); - for ($i = 0; $i < 25; $i++) { - $this->assertEquals($expectedTemplates[$i]['name'], $templates['body']['templates'][$i]['name']); - $this->assertEquals($expectedTemplates[$i]['id'], $templates['body']['templates'][$i]['id']); - $this->assertEquals($expectedTemplates[$i]['icon'], $templates['body']['templates'][$i]['icon']); - $this->assertEquals($expectedTemplates[$i]['tagline'], $templates['body']['templates'][$i]['tagline']); - $this->assertEquals($expectedTemplates[$i]['useCases'], $templates['body']['templates'][$i]['useCases']); - $this->assertEquals($expectedTemplates[$i]['vcsProvider'], $templates['body']['templates'][$i]['vcsProvider']); - $this->assertEquals($expectedTemplates[$i]['runtimes'], $templates['body']['templates'][$i]['runtimes']); - $this->assertEquals($expectedTemplates[$i]['variables'], $templates['body']['templates'][$i]['variables']); - if (array_key_exists('scopes', $expectedTemplates[$i])) { - $this->assertEquals($expectedTemplates[$i]['scopes'], $templates['body']['templates'][$i]['scopes']); - } + foreach ($templates['body']['templates'] as $template) { + $this->assertArrayHasKey('name', $template); + $this->assertArrayHasKey('id', $template); + $this->assertArrayHasKey('icon', $template); + $this->assertArrayHasKey('tagline', $template); + $this->assertArrayHasKey('useCases', $template); + $this->assertArrayHasKey('vcsProvider', $template); + $this->assertArrayHasKey('runtimes', $template); + $this->assertArrayHasKey('variables', $template); + $this->assertArrayHasKey('scopes', $template); } - $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + // List templates with pagination + $templatesOffset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'limit' => 1, 'offset' => 2 ]); + $this->assertEquals(200, $templatesOffset['headers']['status-code']); + $this->assertEquals(1, $templatesOffset['body']['total']); + $this->assertEquals($templates['body']['templates'][2]['id'], $templatesOffset['body']['templates'][0]['id']); - $this->assertEquals(200, $templates_offset['headers']['status-code']); - $this->assertEquals(1, $templates_offset['body']['total']); - // assert that offset works as expected - $this->assertEquals($templates['body']['templates'][2]['id'], $templates_offset['body']['templates'][0]['id']); - + // List templates with filters $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'useCases' => ['starter', 'ai'], 'runtimes' => ['bun-1.0', 'dart-2.16'] ]); - $this->assertEquals(200, $templates['headers']['status-code']); $this->assertGreaterThanOrEqual(3, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); @@ -936,6 +417,7 @@ class FunctionsCustomClientTest extends Scope $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); $this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); + // List templates with pagination and filters $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -958,16 +440,16 @@ class FunctionsCustomClientTest extends Scope /** * Test for FAILURE */ + // List templates with invalid limit $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', ], $this->getHeaders()), [ 'limit' => 5001, 'offset' => 10, ]); - $this->assertEquals(400, $templates['headers']['status-code']); - $this->assertEquals('Invalid `limit` param: Value must be a valid range between 1 and 5,000', $templates['body']['message']); + // List templates with invalid offset $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -975,9 +457,7 @@ class FunctionsCustomClientTest extends Scope 'limit' => 5, 'offset' => 5001, ]); - $this->assertEquals(400, $templates['headers']['status-code']); - $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']); } public function testGetTemplate() @@ -985,10 +465,7 @@ class FunctionsCustomClientTest extends Scope /** * Test for SUCCESS */ - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/query-neo4j-auradb', array_merge([ - 'content-type' => 'application/json', - ], $this->getHeaders()), []); - + $template = $this->getTemplate('query-neo4j-auradb'); $this->assertEquals(200, $template['headers']['status-code']); $this->assertIsArray($template['body']); $this->assertEquals('query-neo4j-auradb', $template['body']['id']); @@ -997,15 +474,13 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('Graph database with focus on relations between data.', $template['body']['tagline']); $this->assertEquals(['databases'], $template['body']['useCases']); $this->assertEquals('github', $template['body']['vcsProvider']); + $this->assertIsArray($template['body']['runtimes']); + $this->assertIsArray($template['body']['scopes']); /** * Test for FAILURE */ - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/invalid-template-id', array_merge([ - 'content-type' => 'application/json', - ], $this->getHeaders()), []); - + $template = $this->getTemplate('invalid-template-id'); $this->assertEquals(404, $template['headers']['status-code']); - $this->assertEquals('Function Template with the requested ID could not be found.', $template['body']['message']); } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 193e99d89e..8fe4aedca7 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -388,12 +388,9 @@ class FunctionsCustomServerTest extends Scope public function testCreateDeploymentFromCLI() { - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $function = $this->createFunction([ 'functionId' => ID::unique(), - 'name' => 'Test', + 'name' => 'test-cli-deployment', 'execute' => [Role::user($this->getUser()['$id'])->toString()], 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', @@ -401,210 +398,127 @@ class FunctionsCustomServerTest extends Scope 'users.*.create', 'users.*.delete', ], - 'schedule' => '0 0 1 1 *', + 'schedule' => '0 0 1 1 *', // Once a year 'timeout' => 10, ]); + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); - $functionId = $function['body']['$id']; - - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/deployments', [ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - 'x-sdk-language' => 'cli', - ], [ + $deployment = $this->createDeployment($functionId, [ 'entrypoint' => 'index.php', - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; + ], cli: true); + $deploymentId = $deployment['body']['$id']; $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); - $this->assertDeployment($function['body']['$id'], $deploymentId); - - $functionDetails = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - - $this->assertEquals(200, $functionDetails['headers']['status-code']); - $this->assertEquals('cli', $functionDetails['body']['type']); + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('cli', $deployment['body']['type']); } public function testCreateDeploymentFromTemplate() { - $runtimeName = 'php-8.0'; - // Fetch starter template (used to create function later) - $template = $this->client->call(Client::METHOD_GET, '/functions/templates/starter', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $starterTemplate = $this->getTemplate('starter'); + $this->assertEquals(200, $starterTemplate['headers']['status-code']); - $this->assertEquals(200, $template['headers']['status-code']); + $phpRuntime = array_values(array_filter($starterTemplate['body']['runtimes'], function ($runtime) { + return $runtime['name'] === 'php-8.0'; + }))[0]; - $entrypoint = null; - $rootDirectory = null; - $commands = null; - foreach ($template['body']['runtimes'] as $runtime) { - if ($runtime["name"] !== $runtimeName) { - continue; - } - - $entrypoint = $runtime["entrypoint"]; - $rootDirectory = $runtime["providerRootDirectory"]; - $commands = $runtime["commands"]; - break; - } - - $this->assertNotNull($entrypoint); - - /** - * If below test ever starts failing, it means temaplate used in - * this test now has some variables. This test currently doesnt test variables. - * Remove bellow assertion and update test to crete variable, - * and ensure variable works as expected in execution. - */ - $this->assertEmpty($template['body']['variables']); - - // Create function using settings from template. - // Deployment is automatically created from template inside endpoint - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders()), [ - 'functionId' => ID::unique(), - 'name' => $template['body']['name'], - 'runtime' => $runtimeName, - 'execute' => $template['body']['permissions'], - 'entrypoint' => $entrypoint, - 'events' => $template['body']['events'], - 'schedule' => $template['body']['cron'], - 'timeout' => $template['body']['timeout'], - 'commands' => $commands, - 'scopes' => $template['body']['scopes'], - 'templateRepository' => $template['body']['providerRepositoryId'], - 'templateOwner' => $template['body']['providerOwner'], - 'templateRootDirectory' => $rootDirectory, - 'templateVersion' => $template['body']['providerVersion'], - ]); + // If this fails, the template has variables, and this test needs to be updated + $this->assertEmpty($starterTemplate['body']['variables']); + $function = $this->createFunction( + [ + 'functionId' => ID::unique(), + 'name' => $starterTemplate['body']['name'], + 'runtime' => 'php-8.0', + 'execute' => $starterTemplate['body']['permissions'], + 'entrypoint' => $phpRuntime['entrypoint'], + 'events' => $starterTemplate['body']['events'], + 'schedule' => $starterTemplate['body']['cron'], + 'timeout' => $starterTemplate['body']['timeout'], + 'commands' => $phpRuntime['commands'], + 'scopes' => $starterTemplate['body']['scopes'], + 'templateRepository' => $starterTemplate['body']['providerRepositoryId'], + 'templateOwner' => $starterTemplate['body']['providerOwner'], + 'templateRootDirectory' => $phpRuntime['providerRootDirectory'], + 'templateVersion' => $starterTemplate['body']['providerVersion'], + ] + ); + $functionId = $function['body']['$id']; $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); - $functionId = $function['body']['$id']; - - // List deployments so we can await deployment build - $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - + $deployments = $this->listDeployments($functionId); $this->assertEquals(200, $deployments['headers']['status-code']); $this->assertEquals(1, $deployments['body']['total']); - $this->assertNotEmpty($deployments['body']['deployments'][0]['$id']); - $this->assertEquals(0, $deployments['body']['deployments'][0]['size']); - $deploymentId = $deployments['body']['deployments'][0]['$id']; + $lastDeployment = $deployments['body']['deployments'][0]; + $this->assertNotEmpty($lastDeployment['$id']); + $this->assertEquals(0, $lastDeployment['size']); - // Wait for deployment build to finish - // Deployment is automatically activated - $this->assertDeployment($function['body']['$id'], $deploymentId); + $deploymentId = $lastDeployment['$id']; + $this->assertDeploymentStatus($function['body']['$id'], $deploymentId, 'ready'); - $deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], []); - $this->assertGreaterThan(0, $deployments['body']['size']); - - $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders()), []); + $deployment = $this->getDeployment($function['body']['$id'], $deploymentId); + $this->assertGreaterThan(0, $deployment['body']['size']); + $function = $this->getFunction($functionId); $this->assertEquals(200, $function['headers']['status-code']); $this->assertEquals($deploymentId, $function['body']['deployment']); - // Execute function to ensure starter code is used - // Also tests if dynamic keys works as expected - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'path' => '/ping' + // Test starter code is used and that dynamic keys work + $execution = $this->createExecution($functionId, [ + 'path' => '/ping', ]); - $this->assertEquals(201, $execution['headers']['status-code']); $this->assertEquals("completed", $execution['body']['status']); $this->assertEquals(200, $execution['body']['responseStatusCode']); $this->assertEquals("Pong", $execution['body']['responseBody']); $this->assertEmpty($execution['body']['errors']); - // Get users to ensure execution logged correct total users + // Test execution logged correct total users $users = $this->client->call(Client::METHOD_GET, '/users', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'], ], $this->getHeaders()), []); - $this->assertEquals(200, $users['headers']['status-code']); $this->assertIsInt($users['body']['total']); - $totalusers = $users['body']['total']; - - $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']); + $totalUsers = $users['body']['total']; + $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); // Execute function again but async - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $execution = $this->createExecution($functionId, [ 'path' => '/ping', 'async' => true ]); - + $executionId = $execution['body']['$id']; $this->assertEquals(202, $execution['headers']['status-code']); $this->assertNotEmpty($execution['body']['$id']); $this->assertEquals('waiting', $execution['body']['status']); - $executionId = $execution['body']['$id']; - // Wait for async execuntion to finish - sleep(5); + $this->assertEventually(function () use ($functionId, $executionId, $totalUsers) { + $execution = $this->getExecution($functionId, $executionId); - // Ensure execution was successful - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), []); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals("completed", $execution['body']['status']); + $this->assertEquals(200, $execution['body']['responseStatusCode']); + $this->assertEmpty($execution['body']['responseBody']); + $this->assertEmpty($execution['body']['errors']); + $this->assertStringContainsString("Total users: " . $totalUsers, $execution['body']['logs']); + }, 100000, 250); - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals("completed", $execution['body']['status']); - $this->assertEquals(200, $execution['body']['responseStatusCode']); - $this->assertEmpty($execution['body']['responseBody']); - $this->assertEmpty($execution['body']['errors']); - $this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']); - - // Cleanup : Delete function - $response = $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->assertEquals(204, $response['headers']['status-code']); + $function = $this->deleteFunction($functionId); + $this->assertEquals(204, $function['headers']['status-code']); } /** @@ -615,15 +529,11 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); @@ -640,7 +550,7 @@ class FunctionsCustomServerTest extends Scope 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => 'false' ]); @@ -675,16 +585,11 @@ class FunctionsCustomServerTest extends Scope */ public function testCancelDeploymentBuild($data): void { - // Create a new deployment to cancel - $folder = 'php'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'code' => $this->packageFunction('php'), 'activate' => true ]); @@ -1406,16 +1311,6 @@ class FunctionsCustomServerTest extends Scope $executionId = $execution['body']['$id'] ?? ''; $this->assertEquals(202, $execution['headers']['status-code']); - $this->assertEventually(function () use ($data, $executionId) { - $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]); - - $this->assertEquals(200, $execution['headers']['status-code']); - $this->assertEquals('waiting', $execution['body']['status']); - }, 10000, 500); - $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $executionId, [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1580,87 +1475,41 @@ class FunctionsCustomServerTest extends Scope public function testTimeout() { - $name = 'php-8.0'; - $entrypoint = 'index.php'; - $timeout = 15; - $folder = 'timeout'; - $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; - $this->packageCode($folder); - - $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'functionId' => ID::unique(), - 'name' => 'Test ' . $name, - 'runtime' => $name, - 'entrypoint' => $entrypoint, + $function = $this->createFunction([ + 'name' => 'timeout-php-8.0', + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', 'events' => [], - 'schedule' => '* * * * *', // execute every minute - 'timeout' => $timeout, + 'schedule' => '*/10 * * * *', // Execute every 10 seconds + 'timeout' => 15, ]); - - $functionId = $function['body']['$id'] ?? ''; - + $functionId = $function['body']['id']; $this->assertEquals(201, $function['headers']['status-code']); - $this->assertEquals('* * * * *', $function['body']['schedule']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ - 'content-type' => 'multipart/form-data', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'entrypoint' => $entrypoint, - 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), - 'activate' => true, - ]); - - $deploymentId = $deployment['body']['$id'] ?? ''; - - $this->assertEquals(202, $deployment['headers']['status-code']); - - $this->assertDeployment($functionId, $deploymentId); - - $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'async' => true, - ]); + $deployment = $this->createDeployment($functionId, 'php'); + $deploymentId = $deployment['body']['$id']; + $this->assertDeploymentStatus($functionId, $deploymentId, 'ready'); + $execution = $this->createExecution($functionId, ['async' => true]); $executionId = $execution['body']['$id'] ?? ''; - $this->assertEquals(202, $execution['headers']['status-code']); - $executionData = null; + $this->assertEventually(function () use ($functionId, $executionId) { + $execution = $this->getExecution($functionId, $executionId); - $this->assertEventually(function () use ($functionId, $executionId, &$executionData) { - $executionData = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); + $this->assertEquals(200, $execution['headers']['status-code']); + $this->assertEquals('failed', $execution['body']['status']); + $this->assertEquals('failed', $execution['body']['status']); + $this->assertEquals(500, $execution['body']['responseStatusCode']); + $this->assertGreaterThan(2, $execution['body']['duration']); + $this->assertLessThan(20, $execution['body']['duration']); + $this->assertEquals('', $execution['body']['responseBody']); + $this->assertEquals('', $execution['body']['logs']); + $this->assertStringContainsString('timed out', $execution['body']['errors']); + }, 20000, 500); - $this->assertEquals(200, $executionData['headers']['status-code']); - $this->assertEquals('failed', $executionData['body']['status']); - - if ($executionData['body']['status'] !== 'failed') { - } - }, 30000, 1000); - - $this->assertEquals('failed', $executionData['body']['status']); - $this->assertEquals(500, $executionData['body']['responseStatusCode']); - $this->assertGreaterThan(2, $executionData['body']['duration']); - $this->assertLessThan(20, $executionData['body']['duration']); - $this->assertEquals('', $executionData['body']['responseBody']); - $this->assertEquals('', $executionData['body']['logs']); - $this->assertStringContainsString('timed out', $executionData['body']['errors']); - - $scheduledExecutionData = null; - - $this->assertEventually(function () use ($functionId, &$scheduledExecutionData) { - $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ + $this->assertEventually(function () use ($functionId) { + $executions = $this->listExecutions($functionId, [ 'queries' => [ Query::equal('trigger', ['schedule'])->toString(), ], @@ -1669,19 +1518,12 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $executions['headers']['status-code']); $this->assertEquals(1, $executions['body']['total']); - $scheduledExecutionData = $executions['body']['executions'][0]; - }, 140000, 1000); // Wait up to 140 seconds, checking every 1 second + $lastExecution = $executions['body']['executions'][0]; + $this->assertEquals('schedule', $lastExecution['trigger']); + }, 20000, 500); - $this->assertEquals('schedule', $scheduledExecutionData['trigger']); - - // Cleanup : Delete function - $response = $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->assertEquals(204, $response['headers']['status-code']); + $function = $this->deleteFunction($functionId); + $this->assertEquals(204, $function['headers']['status-code']); }