diff --git a/app/config/errors.php b/app/config/errors.php index 4a36cee152..d21f503d5f 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -868,6 +868,11 @@ return [ 'description' => 'Variable with the same ID already exists in this project. Try again with a different ID.', 'code' => 409, ], + Exception::VARIABLE_CANNOT_UNSET_SECRET => [ + 'name' => Exception::VARIABLE_CANNOT_UNSET_SECRET, + 'description' => 'Secret variables cannot be marked as non-secret. Please re-create the variable if this is your intention.', + 'code' => 400, + ], Exception::GRAPHQL_NO_QUERY => [ 'name' => Exception::GRAPHQL_NO_QUERY, 'description' => 'Param "query" is not optional.', diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 888ff35382..f6c9d472cf 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1585,7 +1585,7 @@ App::post('/v1/functions/:functionId/variables') ->param('functionId', '', new UID(), 'Function unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) - ->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true) + ->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') @@ -1729,10 +1729,11 @@ App::put('/v1/functions/:functionId/variables/:variableId') ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true) + ->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only functions can read them during build and runtime.', true) ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') - ->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) { + ->action(function (string $functionId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) { $function = $dbForProject->getDocument('functions', $functionId); @@ -1749,9 +1750,14 @@ App::put('/v1/functions/:functionId/variables/:variableId') throw new Exception(Exception::VARIABLE_NOT_FOUND); } + if ($variable->getAttribute('secret') === true && $secret === false) { + throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET); + } + $variable ->setAttribute('key', $key) ->setAttribute('value', $value ?? $variable->getAttribute('value')) + ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) ->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function'])); try { diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php index 0544760e73..22c3337203 100644 --- a/app/controllers/api/project.php +++ b/app/controllers/api/project.php @@ -388,7 +388,7 @@ App::post('/v1/project/variables') )) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) - ->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true) + ->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true) ->inject('project') ->inject('response') ->inject('dbForProject') @@ -509,19 +509,25 @@ App::put('/v1/project/variables/:variableId') ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true) + ->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true) ->inject('project') ->inject('response') ->inject('dbForProject') ->inject('dbForPlatform') - ->action(function (string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { + ->action(function (string $variableId, string $key, ?string $value, ?bool $secret, Document $project, Response $response, Database $dbForProject, Database $dbForPlatform) { $variable = $dbForProject->getDocument('variables', $variableId); if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceType') !== 'project') { throw new Exception(Exception::VARIABLE_NOT_FOUND); } + if ($variable->getAttribute('secret') === true && $secret === false) { + throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET); + } + $variable ->setAttribute('key', $key) ->setAttribute('value', $value ?? $variable->getAttribute('value')) + ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) ->setAttribute('search', implode(' ', [$variableId, $key, 'project'])); try { diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 2992c75987..3474920669 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -255,6 +255,7 @@ class Exception extends \Exception /** Variables */ public const VARIABLE_NOT_FOUND = 'variable_not_found'; public const VARIABLE_ALREADY_EXISTS = 'variable_already_exists'; + public const VARIABLE_CANNOT_UNSET_SECRET = 'variable_cannot_unset_secret'; /** Platform */ public const PLATFORM_NOT_FOUND = 'platform_not_found'; diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php index 1207f6c1c4..2d3cf76f5c 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Create.php @@ -56,14 +56,13 @@ class Create extends Base ->param('siteId', '', new UID(), 'Site unique ID.', false) ->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false) - ->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true) + ->param('secret', true, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true) ->inject('response') ->inject('dbForProject') - ->inject('dbForPlatform') ->callback([$this, 'action']); } - public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) + public function action(string $siteId, string $key, string $value, bool $secret, Response $response, Database $dbForProject) { $site = $dbForProject->getDocument('sites', $siteId); diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php index d9d70e4bdf..4040a8ae71 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Variables/Update.php @@ -13,6 +13,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException; use Utopia\Database\Validator\UID; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; +use Utopia\Validator\Boolean; use Utopia\Validator\Text; class Update extends Base @@ -52,12 +53,13 @@ class Update extends Base ->param('variableId', '', new UID(), 'Variable unique ID.', false) ->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false) ->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true) + ->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only sites can read them during build and runtime.', true) ->inject('response') ->inject('dbForProject') ->callback([$this, 'action']); } - public function action(string $siteId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject) + public function action(string $siteId, string $variableId, string $key, ?string $value, ?bool $secret, Response $response, Database $dbForProject) { $site = $dbForProject->getDocument('sites', $siteId); @@ -74,9 +76,14 @@ class Update extends Base throw new Exception(Exception::VARIABLE_NOT_FOUND); } + if ($variable->getAttribute('secret') === true && $secret === false) { + throw new Exception(Exception::VARIABLE_CANNOT_UNSET_SECRET); + } + $variable ->setAttribute('key', $key) ->setAttribute('value', $value ?? $variable->getAttribute('value')) + ->setAttribute('secret', $secret ?? $variable->getAttribute('secret')) ->setAttribute('search', implode(' ', [$variableId, $site->getId(), $key, 'site'])); try { diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index a1bb8f2b21..0986208bef 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -82,6 +82,46 @@ trait FunctionsBase return $variable; } + protected function getVariable(string $functionId, string $variableId): mixed + { + $variable = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $variable; + } + + protected function updateVariable(string $functionId, string $variableId, mixed $params): mixed + { + $variable = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $variable; + } + + protected function listVariables(string $functionId, mixed $params = []): mixed + { + $variables = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $variables; + } + + protected function deleteVariable(string $functionId, string $variableId): mixed + { + $variable = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $variable; + } + protected function getFunction(string $functionId): mixed { $function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([ diff --git a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php index 0708d40aab..9b37fad394 100644 --- a/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsConsoleClientTest.php @@ -110,7 +110,8 @@ class FunctionsConsoleClientTest extends Scope $data['functionId'], [ 'key' => 'APP_TEST', - 'value' => 'TESTINGVALUE' + 'value' => 'TESTINGVALUE', + 'secret' => false ] ); @@ -131,6 +132,7 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals(201, $variable['headers']['status-code']); $this->assertEquals('APP_TEST_1', $variable['body']['key']); $this->assertEmpty($variable['body']['value']); + $this->assertTrue($variable['body']['secret']); $secretVariableId = $variable['body']['$id']; @@ -142,7 +144,8 @@ class FunctionsConsoleClientTest extends Scope $data['functionId'], [ 'key' => 'APP_TEST', - 'value' => 'ANOTHERTESTINGVALUE' + 'value' => 'ANOTHERTESTINGVALUE', + 'secret' => false ] ); @@ -320,6 +323,41 @@ class FunctionsConsoleClientTest extends Scope $this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']); $this->assertEquals("TESTINGVALUEUPDATED", $variable['body']['value']); + // convert non-secret variable to secret + $response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE_2', + 'secret' => true + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_2", $response['body']['key']); + $this->assertEmpty($response['body']['value']); + $this->assertTrue($response['body']['secret']); + + $variable = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertEquals("APP_TEST_UPDATE_2", $variable['body']['key']); + $this->assertEmpty($variable['body']['value']); + $this->assertTrue($variable['body']['secret']); + + // convert secret variable to non-secret + $response = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'] . '/variables/' . $data['variableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE', + 'secret' => false + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + /** * Test for FAILURE */ @@ -410,4 +448,53 @@ class FunctionsConsoleClientTest extends Scope return $data; } + + public function testVariableE2E(): void + { + $function = $this->createFunction([ + 'functionId' => ID::unique(), + 'runtime' => 'node-18.0', + 'name' => 'Variable E2E Test', + 'entrypoint' => 'index.js', + 'logging' => false, + 'execute' => ['any'] + ]); + + $this->assertEquals(201, $function['headers']['status-code']); + $this->assertFalse($function['body']['logging']); + $this->assertNotEmpty($function['body']['$id']); + + $functionId = $function['body']['$id'] ?? ''; + + // create variable + $variable = $this->createVariable($functionId, [ + 'key' => 'CUSTOM_VARIABLE', + 'value' => 'a_secret_value', + 'secret' => true, + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('CUSTOM_VARIABLE', $variable['body']['key']); + $this->assertEquals('', $variable['body']['value']); + $this->assertEquals(true, $variable['body']['secret']); + + $deploymentId = $this->setupDeployment($functionId, [ + 'entrypoint' => 'index.js', + 'code' => $this->packageFunction('node'), + 'activate' => true + ]); + + $this->assertNotEmpty($deploymentId); + + $execution = $this->createExecution($functionId); + + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertEmpty($execution['body']['logs']); + $this->assertEmpty($execution['body']['errors']); + $body = json_decode($execution['body']['responseBody']); + $this->assertEquals('a_secret_value', $body->CUSTOM_VARIABLE); + + $this->cleanupFunction($functionId); + } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 712d6d3948..98e03af1b8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -38,7 +38,7 @@ class FunctionsCustomServerTest extends Scope 'timeout' => 10, ]); - $functionId = $functionId = $function['body']['$id'] ?? ''; + $functionId = $function['body']['$id'] ?? ''; $dateValidator = new DatetimeValidator(); $this->assertEquals(201, $function['headers']['status-code']); @@ -356,7 +356,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); $this->assertNotEmpty($function['body']['$id']); - $functionId = $functionId = $function['body']['$id'] ?? ''; + $functionId = $function['body']['$id'] ?? ''; $deployments = $this->listDeployments($functionId); @@ -1898,7 +1898,7 @@ class FunctionsCustomServerTest extends Scope $this->assertFalse($function['body']['logging']); $this->assertNotEmpty($function['body']['$id']); - $functionId = $functionId = $function['body']['$id'] ?? ''; + $functionId = $function['body']['$id'] ?? ''; $this->setupDeployment($functionId, [ 'code' => $this->packageFunction('node'), diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 34c1142619..ed9171e46a 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3923,10 +3923,14 @@ class ProjectsConsoleClientTest extends Scope 'x-appwrite-mode' => 'admin', ], $this->getHeaders()), [ 'key' => 'APP_TEST', - 'value' => 'TESTINGVALUE' + 'value' => 'TESTINGVALUE', + 'secret' => false ]); $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertEquals('APP_TEST', $variable['body']['key']); + $this->assertEquals('TESTINGVALUE', $variable['body']['value']); + $this->assertFalse($variable['body']['secret']); $variableId = $variable['body']['$id']; // test for secret variable @@ -4047,6 +4051,7 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals("APP_TEST_1", $response['body']['key']); $this->assertEmpty($response['body']['value']); + $this->assertTrue($response['body']['secret']); /** * Test for FAILURE @@ -4118,6 +4123,17 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals("APP_TEST_UPDATE_1", $variable['body']['key']); $this->assertEmpty($variable['body']['value']); + $response = $this->client->call(Client::METHOD_PUT, '/project/variables/' . $data['secretVariableId'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $data['projectId'], + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'key' => 'APP_TEST_UPDATE_1', + 'secret' => false, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + $response = $this->client->call(Client::METHOD_GET, '/project/variables', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $data['projectId'], diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 277064ead5..d76e34fd24 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -104,6 +104,46 @@ trait SitesBase return $variable; } + protected function getVariable(string $siteId, string $variableId): mixed + { + $variable = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $variable; + } + + protected function listVariables(string $siteId, mixed $params = []): mixed + { + $variables = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId . '/variables', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $variables; + } + + protected function updateVariable(string $siteId, string $variableId, mixed $params): mixed + { + $variable = $this->client->call(Client::METHOD_PUT, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $variable; + } + + protected function deleteVariable(string $siteId, string $variableId): mixed + { + $variable = $this->client->call(Client::METHOD_DELETE, '/sites/' . $siteId . '/variables/' . $variableId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + return $variable; + } + protected function getSite(string $siteId): mixed { $site = $this->client->call(Client::METHOD_GET, '/sites/' . $siteId, array_merge([ diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index ff3aa7b348..5d3e9c81ef 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -65,6 +65,193 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } + public function testVariables(): void + { + $site = $this->createSite([ + 'buildRuntime' => 'ssr-22', + 'fallbackFile' => null, + 'framework' => 'other', + 'name' => 'Test Site', + 'outputDirectory' => './', + 'siteId' => ID::unique() + ]); + + $siteId = $site['body']['$id'] ?? ''; + + $this->assertEquals(201, $site['headers']['status-code']); + $this->assertNotEmpty($site['body']['$id']); + $this->assertEquals('Test Site', $site['body']['name']); + + $variable = $this->createVariable($siteId, [ + 'key' => 'siteKey1', + 'value' => 'siteValue1', + 'secret' => false, + ]); + + $this->assertEquals(201, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1', $variable['body']['key']); + $this->assertEquals('siteValue1', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $variable2 = $this->createVariable($siteId, [ + 'key' => 'siteKey2', + 'value' => 'siteValue2', + 'secret' => false, + ]); + + $this->assertEquals(201, $variable2['headers']['status-code']); + $this->assertNotEmpty($variable2['body']['$id']); + $this->assertEquals('siteKey2', $variable2['body']['key']); + $this->assertEquals('siteValue2', $variable2['body']['value']); + $this->assertEquals(false, $variable2['body']['secret']); + + $secretVariable = $this->createVariable($siteId, [ + 'key' => 'siteKey3', + 'value' => 'siteValue3', + 'secret' => true, + ]); + + $this->assertEquals(201, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variable = $this->getVariable($siteId, $variable['body']['$id']); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1', $variable['body']['key']); + $this->assertEquals('siteValue1', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ + 'key' => 'siteKey1Updated', + 'value' => 'siteValue1Updated', + ]); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1Updated', $variable['body']['key']); + $this->assertEquals('siteValue1Updated', $variable['body']['value']); + $this->assertEquals(false, $variable['body']['secret']); + + $variable = $this->updateVariable($siteId, $variable['body']['$id'], [ + 'key' => 'siteKey1Updated', + 'secret' => true, + ]); + + $this->assertEquals(200, $variable['headers']['status-code']); + $this->assertNotEmpty($variable['body']['$id']); + $this->assertEquals('siteKey1Updated', $variable['body']['key']); + $this->assertEquals('', $variable['body']['value']); + $this->assertEquals(true, $variable['body']['secret']); + + $secretVariable = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ + 'key' => 'siteKey3', + 'value' => 'siteValue3Updated', + ]); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $response = $this->updateVariable($siteId, $secretVariable['body']['$id'], [ + 'key' => 'siteKey3', + 'secret' => false, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $secretVariable = $this->getVariable($siteId, $secretVariable['body']['$id']); + + $this->assertEquals(200, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('siteKey3', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $variables = $this->listVariables($siteId); + + $this->assertEquals(200, $variables['headers']['status-code']); + $this->assertCount(3, $variables['body']['variables']); + + $response = $this->deleteVariable($siteId, $variable['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->deleteVariable($siteId, $variable2['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + $response = $this->deleteVariable($siteId, $secretVariable['body']['$id']); + $this->assertEquals(204, $response['headers']['status-code']); + + $variables = $this->listVariables($siteId); + + $this->assertEquals(200, $variables['headers']['status-code']); + $this->assertCount(0, $variables['body']['variables']); + + $this->cleanupSite($siteId); + } + + public function testVariablesE2E(): void + { + $siteId = $this->setupSite([ + 'siteId' => ID::unique(), + 'name' => 'Astro site', + 'framework' => 'astro', + 'adapter' => 'ssr', + 'buildRuntime' => 'ssr-22', + 'outputDirectory' => './dist', + 'buildCommand' => 'npm run build', + 'installCommand' => 'npm install', + 'fallbackFile' => '', + ]); + + $this->assertNotEmpty($siteId); + + $secretVariable = $this->createVariable($siteId, [ + 'key' => 'name', + 'value' => 'Appwrite', + ]); + + $this->assertEquals(201, $secretVariable['headers']['status-code']); + $this->assertNotEmpty($secretVariable['body']['$id']); + $this->assertEquals('name', $secretVariable['body']['key']); + $this->assertEquals('', $secretVariable['body']['value']); + $this->assertEquals(true, $secretVariable['body']['secret']); + + $deploymentId = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('astro'), + 'activate' => 'true' + ]); + + $this->assertNotEmpty($deploymentId); + + $domain = $this->getSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertStringContainsString("Env variable is Appwrite", $response['body']); + $this->assertStringNotContainsString("Variable not found", $response['body']); + + $this->cleanupSite($siteId); + } + public function testListSites(): void { /** diff --git a/tests/resources/sites/astro/astro.config.mjs b/tests/resources/sites/astro/astro.config.mjs new file mode 100644 index 0000000000..54bee6ca84 --- /dev/null +++ b/tests/resources/sites/astro/astro.config.mjs @@ -0,0 +1,10 @@ +import { defineConfig } from 'astro/config'; +import node from '@astrojs/node'; +import 'dotenv/config'; + +export default defineConfig({ + output: 'server', + adapter: node({ + mode: 'standalone' + }), +}); diff --git a/tests/resources/sites/astro/package.json b/tests/resources/sites/astro/package.json new file mode 100644 index 0000000000..a87c37d014 --- /dev/null +++ b/tests/resources/sites/astro/package.json @@ -0,0 +1,16 @@ +{ + "name": "my-astro-app", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/node": "^9.0.2", + "astro": "^5.2.5", + "dotenv": "^16.4.7" + } +} diff --git a/tests/resources/sites/astro/src/pages/index.astro b/tests/resources/sites/astro/src/pages/index.astro new file mode 100644 index 0000000000..cc8fd9411f --- /dev/null +++ b/tests/resources/sites/astro/src/pages/index.astro @@ -0,0 +1,15 @@ +--- +const value = import.meta.env.name || 'Variable not found'; +--- + + + +
+ + +Env variable is {value}
+ + diff --git a/tests/resources/sites/astro/tsconfig.json b/tests/resources/sites/astro/tsconfig.json new file mode 100644 index 0000000000..8bf91d3bb9 --- /dev/null +++ b/tests/resources/sites/astro/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +}