diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 4a36616f2a..8fc05d314f 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -5,7 +5,6 @@ use Appwrite\Event\Build; use Appwrite\Event\Delete; use Appwrite\Extend\Exception; use Appwrite\Network\Validator\Redirect; -use Appwrite\Platform\Modules\Compute\Base as ComputeBase; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -32,7 +31,10 @@ use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\Queries; use Utopia\Database\Validator\Query\Cursor; +use Utopia\Database\Validator\Query\Limit; +use Utopia\Database\Validator\Query\Offset; use Utopia\Detector\Detection\Framework\Analog; use Utopia\Detector\Detection\Framework\Angular; use Utopia\Detector\Detection\Framework\Astro; @@ -1036,10 +1038,11 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') ->param('installationId', '', new Text(256), 'Installation Id') ->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework') ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) + ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true) ->inject('gitHub') ->inject('response') ->inject('dbForPlatform') - ->action(function (string $installationId, string $type, string $search, GitHub $github, Response $response, Database $dbForPlatform) { + ->action(function (string $installationId, string $type, string $search, array $queries, GitHub $github, Response $response, Database $dbForPlatform) { if (empty($search)) { $search = ""; } @@ -1055,11 +1058,20 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID'); $github->initializeVariables($providerInstallationId, $privateKey, $githubAppId); - $page = 1; - $perPage = 4; + $queries = Query::parseQueries($queries); + $limitQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_LIMIT)); + $offsetQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_OFFSET)); + $limit = !empty($limitQuery) ? $limitQuery->getValue() : 4; + $offset = !empty($offsetQuery) ? $offsetQuery->getValue() : 0; + + if ($offset % $limit !== 0) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'offset must be a multiple of the limit'); + } + + $page = ($offset / $limit) + 1; $owner = $github->getOwnerName($providerInstallationId); - $repos = $github->searchRepositories($owner, $page, $perPage, $search); + ['items' => $repos, 'total' => $total] = $github->searchRepositories($owner, $page, $limit, $search); $repos = \array_map(function ($repo) use ($installation) { $repo['id'] = \strval($repo['id'] ?? ''); @@ -1231,7 +1243,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories') $response->dynamic(new Document([ $type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos, - 'total' => \count($repos), + 'total' => $total, ]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST); }); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 97c5e29f3d..6f01db12b6 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -859,7 +859,7 @@ App::shutdown() * * Therefore, we consider this an anonymous request and create a relevant user. */ - $user = new Document([ + $user = new User([ '$id' => '', 'status' => true, 'type' => ACTIVITY_TYPE_GUEST, diff --git a/app/init/constants.php b/app/init/constants.php index 0cec24b749..ea5c0fb2c5 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -93,6 +93,13 @@ const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite'; const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled'; +/** + * JWT for Resource Tokens. + */ +const RESOURCE_TOKEN_ALGORITHM = 'HS256'; +const RESOURCE_TOKEN_MAX_AGE = 86400 * 365 * 10; /* 10 years */ +const RESOURCE_TOKEN_LEEWAY = 10; // 10 seconds + /** * Token Expiration times. */ diff --git a/app/init/resources.php b/app/init/resources.php index 6114f318ad..49f726d48c 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -1013,7 +1013,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A $tokenJWT = $request->getParam('token'); if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway. + // Use a large but reasonable maxAge to avoid auto-exp when token has no expiry + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway. try { $payload = $jwt->decode($tokenJWT); diff --git a/composer.json b/composer.json index 8e2a21f527..a4b8564dd9 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "utopia-php/swoole": "0.8.*", "utopia-php/system": "0.9.*", "utopia-php/telemetry": "0.1.*", - "utopia-php/vcs": "0.12.*", + "utopia-php/vcs": "0.13.*", "utopia-php/websocket": "0.3.*", "matomo/device-detector": "6.4.*", "dragonmantank/cron-expression": "3.4.*", diff --git a/composer.lock b/composer.lock index d283b0f347..a453dd8b56 100644 --- a/composer.lock +++ b/composer.lock @@ -5212,16 +5212,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.12.0", + "version": "0.13.0", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "28457cf347972c4ec95d3ca77776a4921364a665" + "reference": "c59e21db5ca42014fe2071fec3c2f814efcc86dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/28457cf347972c4ec95d3ca77776a4921364a665", - "reference": "28457cf347972c4ec95d3ca77776a4921364a665", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/c59e21db5ca42014fe2071fec3c2f814efcc86dd", + "reference": "c59e21db5ca42014fe2071fec3c2f814efcc86dd", "shasum": "" }, "require": { @@ -5255,9 +5255,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.12.0" + "source": "https://github.com/utopia-php/vcs/tree/0.13.0" }, - "time": "2025-10-22T12:58:29+00:00" + "time": "2025-11-28T08:42:31+00:00" }, { "name": "utopia-php/websocket", diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index c7bb9d2de6..f31617ce7a 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -13,7 +13,6 @@ use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; -use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Swoole\Request; use Utopia\System\System; diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php index c848be405b..7db03ced5d 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -33,7 +32,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Nullable; use Utopia\Validator\Text; -class Create extends Base +class Create extends Action { use HTTP; @@ -84,7 +83,6 @@ class Create extends Base ->inject('request') ->inject('response') ->inject('dbForProject') - ->inject('dbForPlatform') ->inject('queueForEvents') ->inject('project') ->inject('deviceForFunctions') @@ -104,7 +102,6 @@ class Create extends Base Request $request, Response $response, Database $dbForProject, - Database $dbForPlatform, Event $queueForEvents, Document $project, Device $deviceForFunctions, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php index 947523b220..fe69cc74a6 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Template/Create.php @@ -127,7 +127,6 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php index 9a252491e2..d257b87114 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Vcs/Create.php @@ -107,7 +107,6 @@ class Create extends Base project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 558d3a01aa..95a1f2d365 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -307,14 +307,12 @@ class Create extends Base $template = new Document(); $installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId')); - // TODO: Is this still needed? Can this be removed? $deployment = $this->redeployVcsFunction( request: $request, function: $function, project: $project, installation: $installation, dbForProject: $dbForProject, - dbForPlatform: $dbForPlatform, queueForBuilds: $queueForBuilds, template: $template, github: $github, diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php index c24daa3df2..7aff1888b1 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Create.php @@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Sites\Http\Deployments; use Appwrite\Event\Build; use Appwrite\Event\Event; use Appwrite\Extend\Exception; -use Appwrite\Platform\Modules\Compute\Base; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -33,7 +32,7 @@ use Utopia\Validator\Boolean; use Utopia\Validator\Nullable; use Utopia\Validator\Text; -class Create extends Base +class Create extends Action { use HTTP; diff --git a/src/Appwrite/Utopia/Response/Model/ResourceToken.php b/src/Appwrite/Utopia/Response/Model/ResourceToken.php index 87ab66ab5d..c2b3deb56b 100644 --- a/src/Appwrite/Utopia/Response/Model/ResourceToken.php +++ b/src/Appwrite/Utopia/Response/Model/ResourceToken.php @@ -64,7 +64,7 @@ class ResourceToken extends Model $expire = $document->getAttribute('expire'); // Use a large but reasonable maxAge to avoid auto-exp when we set explicit exp - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // 10 years $payload = [ 'tokenId' => $document->getId(), @@ -73,13 +73,13 @@ class ResourceToken extends Model 'resourceInternalId' => $document->getAttribute('resourceInternalId'), ]; + $createdDate = new \DateTime($document->getCreatedAt()); + $payload['iat'] = $createdDate->getTimestamp(); + // Set explicit expiration in JWT payload if we have an expiry date if ($expire !== null) { $expiryDate = new \DateTime($expire); $payload['exp'] = $expiryDate->getTimestamp(); - } else { - // For infinite expiry, set 'iat' to prevent JWT library from auto-adding 'exp' - $payload['iat'] = time(); } $secret = $jwt->encode($payload); diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index 492226b01a..7403b23a73 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -432,27 +432,4 @@ trait FunctionsBase return $specifications; } - - protected function createFunctionRule(string $functionId, string $domain): mixed - { - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/function', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'functionId' => $functionId, - 'domain' => $domain, - ]); - - return $rule; - } - - protected function getFunctionRule(string $ruleId): mixed - { - $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - return $rule; - } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 74b6ed3e8d..8cc986b072 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -14,7 +14,6 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; use Utopia\Database\Query; use Utopia\Database\Validator\Datetime as DatetimeValidator; -use Utopia\System\System; class FunctionsCustomServerTest extends Scope { @@ -393,10 +392,6 @@ class FunctionsCustomServerTest extends Scope $functionId = $function['body']['$id'] ?? ''; - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $rule = $this->createFunctionRule($functionId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createTemplateDeployment( $functionId, [ @@ -413,20 +408,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getFunctionRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString("The page will update after the build completes.", $response['body']); - - $deployment = $this->getDeployment($functionId, $deployment['body']['$id']); - $this->assertEquals(200, $deployment['headers']['status-code']); // Wait for deployment to be ready $deploymentId = $deployment['body']['$id']; $this->assertEventually(function () use ($functionId, $deploymentId) { @@ -436,6 +417,7 @@ class FunctionsCustomServerTest extends Scope // Verify deployment sizes $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertGreaterThan(0, $deployment['body']['sourceSize']); $this->assertGreaterThan(0, $deployment['body']['buildSize']); $totalSize = $deployment['body']['sourceSize'] + $deployment['body']['buildSize']; @@ -680,10 +662,6 @@ class FunctionsCustomServerTest extends Scope */ $functionId = $data['functionId']; - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - $rule = $this->createFunctionRule($functionId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -695,18 +673,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.js', $deployment['body']['entrypoint']); - $rule = $this->getFunctionRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($functionId, $deploymentIdActive) { @@ -2426,12 +2392,6 @@ class FunctionsCustomServerTest extends Scope $domain = $this->setupFunctionDomain($functionId); $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); - $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -2444,22 +2404,11 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment is still building', $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); // canceled deployment - $functionId = $this->setupFunction([ - 'functionId' => ID::unique(), - 'name' => 'Test Error Pages', - 'runtime' => 'node-22', - 'entrypoint' => 'index.js', - 'timeout' => 15, - 'commands' => 'cd non-existing-directory', - 'execute' => ['any'] - ]); - $domain = $this->setupFunctionDomain($functionId); - $proxyClient->setEndpoint('http://' . $domain); $deployment = $this->createDeployment($functionId, [ 'code' => $this->packageFunction('basic'), 'activate' => true @@ -2477,9 +2426,9 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'] ])); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment build canceled', $response['body']); - $this->assertStringContainsString('This build was canceled and won\'t be deployed.', $response['body']); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); $this->cleanupFunction($functionId); } diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 6abe1b38d9..7eb5d9699c 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -474,27 +474,4 @@ trait SitesBase return $specifications; } - - protected function createSiteRule(string $siteId, string $domain): mixed - { - $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/site', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'siteId' => $siteId, - 'domain' => $domain, - ]); - - return $rule; - } - - protected function getSiteRule(string $ruleId): mixed - { - $rule = $this->client->call(Client::METHOD_GET, '/proxy/rules/' . $ruleId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders())); - - return $rule; - } } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 413fea289f..b7dc9e7334 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -861,10 +861,6 @@ class SitesCustomServerTest extends Scope $this->assertNotNull($siteId); - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); - $rule = $this->createSiteRule($siteId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createDeployment($siteId, [ 'siteId' => $siteId, 'code' => $this->packageSite('static-single-file'), @@ -876,18 +872,6 @@ class SitesCustomServerTest extends Scope $this->assertEquals('waiting', $deployment['body']['status']); $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - $rule = $this->getSiteRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deploymentIdActive = $deployment['body']['$id'] ?? ''; $this->assertEventually(function () use ($siteId, $deploymentIdActive) { @@ -1579,10 +1563,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); - $domain = ID::unique() . '.' . System::getEnv('_APP_DOMAIN_SITES', ''); - $rule = $this->createSiteRule($siteId, $domain); - $this->assertEquals(201, $rule['headers']['status-code']); - $deployment = $this->createTemplateDeployment($siteId, [ 'repository' => $template['providerRepositoryId'], 'owner' => $template['providerOwner'], @@ -1595,18 +1575,6 @@ class SitesCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $rule = $this->getSiteRule($rule['body']['$id']); - $this->assertEquals(200, $rule['headers']['status-code']); - $this->assertEquals($deployment['body']['$id'], $rule['body']['deploymentId']); - - $proxyClient = new Client(); - $proxyClient->setEndpoint('http://' . $domain); - - $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString("Deployment is still building", $response['body']); - $this->assertStringContainsString('The page will update after the build completes.', $response['body']); - $deployment = $this->getDeployment($siteId, $deployment['body']['$id']); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals(0, $deployment['body']['sourceSize']); @@ -1618,6 +1586,10 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($site['body']['deploymentId']); }, 50000, 500); + $domain = $this->setupSiteDomain($siteId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(200, $response['headers']['status-code']); @@ -2612,8 +2584,7 @@ class SitesCustomServerTest extends Scope }, 100000, 500); $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertEquals(400, $response['headers']['status-code']); - $this->assertStringContainsString('Deployment build failed', $response['body']); + $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); $this->cleanupSite($siteId); } @@ -2708,12 +2679,6 @@ class SitesCustomServerTest extends Scope $this->assertNotEmpty($siteId); $domain = $this->setupSiteDomain($siteId); - $proxyClient->setEndpoint('http://' . $domain); - $response = $proxyClient->call(Client::METHOD_GET, '/'); - - $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('No active deployments', $response['body']); - $this->assertStringContainsString('View deployments', $response['body']); // test canceled deployment error page $deployment = $this->createDeployment($siteId, [ @@ -2749,6 +2714,13 @@ class SitesCustomServerTest extends Scope $this->assertStringContainsString("Deployment build canceled", $response['body']); $this->assertStringContainsString("View deployments", $response['body']); + // check site domain for no active deployments + $proxyClient->setEndpoint('http://' . $domain); + $response = $proxyClient->call(Client::METHOD_GET, '/'); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); + $deployment = $this->createDeployment($siteId, [ 'code' => $this->packageSite('static-single-file'), 'activate' => 'true' diff --git a/tests/e2e/Services/Tokens/TokensConsoleClientTest.php b/tests/e2e/Services/Tokens/TokensConsoleClientTest.php index f1480faba0..7a9181b1dc 100644 --- a/tests/e2e/Services/Tokens/TokensConsoleClientTest.php +++ b/tests/e2e/Services/Tokens/TokensConsoleClientTest.php @@ -72,37 +72,45 @@ class TokensConsoleClientTest extends Scope $this->assertEquals(400, $token['headers']['status-code']); $this->assertStringContainsString('Value must be valid date in the future', $token['body']['message']); - // Success case: No expire date - $token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'] - ], $this->getHeaders()), [ - 'expire' => null, - ]); + // Success cases: With & without expiry + $expireList = [null, date('Y-m-d', strtotime("tomorrow"))]; + foreach ($expireList as $expire) { + $token = $this->client->call(Client::METHOD_POST, '/tokens/buckets/' . $bucketId . '/files/' . $fileId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + 'expire' => $expire, + ]); - $this->assertEquals(201, $token['headers']['status-code']); - $this->assertEquals('files', $token['body']['resourceType']); - $this->assertNotEmpty($token['body']['$id']); - $this->assertNotEmpty($token['body']['secret']); + $this->assertEquals(201, $token['headers']['status-code']); + $this->assertEquals('files', $token['body']['resourceType']); + $this->assertNotEmpty($token['body']['$id']); + $this->assertNotEmpty($token['body']['secret']); - // Verify the generated token JWT contains correct resource information - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge - try { - $payload = $jwt->decode($token['body']['secret']); - $this->assertIsArray($payload, 'JWT payload should decode to an array'); - $this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId'); - $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); - $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); - $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + // Verify the generated token JWT contains correct resource information + $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400 * 365 * 10, 10); // 10 years maxAge + try { + $payload = $jwt->decode($token['body']['secret']); + $this->assertIsArray($payload, 'JWT payload should decode to an array'); + $this->assertArrayHasKey('tokenId', $payload, 'JWT payload should contain tokenId'); + $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); + $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); + $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + $this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat'); - $this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); - $this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); - $this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files'); + if (!empty($expire)) { + $this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp'); + } else { + $this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry'); + } - // For newly created tokens without expiry, should not have exp field - $this->assertArrayNotHasKey('exp', $payload, 'JWT payload should not contain exp field for tokens without expiry'); - } catch (JWTException $e) { - $this->fail('Failed to decode JWT: ' . $e->getMessage()); + $this->assertEquals($token['body']['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); + $this->assertEquals($bucketId . ':' . $fileId, $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); + $this->assertEquals('files', $payload['resourceType'], 'JWT resourceType should be files'); + + } catch (JWTException $e) { + $this->fail('Failed to decode JWT: ' . $e->getMessage()); + } } return [ @@ -218,6 +226,11 @@ class TokensConsoleClientTest extends Scope $this->assertArrayHasKey('resourceId', $payload, 'JWT payload should contain resourceId'); $this->assertArrayHasKey('resourceType', $payload, 'JWT payload should contain resourceType'); $this->assertArrayHasKey('resourceInternalId', $payload, 'JWT payload should contain resourceInternalId'); + $this->assertArrayHasKey('iat', $payload, 'JWT payload should contain iat'); + + if (!empty($token['expire'])) { + $this->assertArrayHasKey('exp', $payload, 'JWT payload should contain exp'); + } $this->assertEquals($token['$id'], $payload['tokenId'], 'JWT tokenId should match token ID'); $this->assertEquals($data['bucketId'] . ':' . $data['fileId'], $payload['resourceId'], 'JWT resourceId should match bucketId:fileId format'); diff --git a/tests/e2e/Services/VCS/VCSConsoleClientTest.php b/tests/e2e/Services/VCS/VCSConsoleClientTest.php index 13c3ddb251..963aa5a84b 100644 --- a/tests/e2e/Services/VCS/VCSConsoleClientTest.php +++ b/tests/e2e/Services/VCS/VCSConsoleClientTest.php @@ -10,6 +10,7 @@ use Utopia\Cache\Adapter\None; use Utopia\Cache\Cache; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use Utopia\System\System; use Utopia\VCS\Adapter\Git\GitHub; @@ -316,6 +317,43 @@ class VCSConsoleClientTest extends Scope $this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'appwrite'); $this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'other'); + // with limit and offset + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(1)->toString(), + 'offset' => Query::offset(0)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(1, $repositories['body']['runtimeProviderRepositories']); + $this->assertSame('starter-for-svelte', $repositories['body']['runtimeProviderRepositories'][0]['name']); + + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(2)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(2, $repositories['body']['runtimeProviderRepositories']); + $this->assertSame('appwrite', $repositories['body']['runtimeProviderRepositories'][0]['name']); + $this->assertSame('ruby-starter', $repositories['body']['runtimeProviderRepositories'][1]['name']); + + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(100)->toString() + ]); + $this->assertSame(200, $repositories['headers']['status-code']); + $this->assertSame(4, $repositories['body']['total']); + $this->assertCount(0, $repositories['body']['runtimeProviderRepositories']); + // TODO: If you are about to add another check, rewrite this to @provideScenarios /** @@ -338,6 +376,17 @@ class VCSConsoleClientTest extends Scope $this->assertEquals(400, $repositories['headers']['status-code']); + // invalid offset + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'type' => 'runtime', + 'limit' => Query::limit(2)->toString(), + 'offset' => Query::offset(1)->toString() + ]); + $this->assertEquals(400, $repositories['headers']['status-code']); + $this->assertEquals('offset must be a multiple of the limit', $repositories['body']['message']); + $repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([ 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [