diff --git a/app/controllers/general.php b/app/controllers/general.php index 03f53b9582..079b7f642b 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -313,7 +313,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw if ($type === 'function') { $permissions = $resource->getAttribute('execute'); if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) { - throw new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView); + $exception = new AppwriteException(AppwriteException::FUNCTION_EXECUTE_PERMISSION_MISSING, view: $errorView); + $exception->addCTA('View settings', $url . '/console/project-' . $project->getId() . '/functions/function-' . $resource->getId() . '/settings'); + throw $exception; } } @@ -548,6 +550,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw foreach ($executionResponse['headers'] as $key => $value) { if (\strtolower($key) === 'content-length') { $executionResponse['headers'][$key] = \strlen($executionResponse['body']); + } elseif (\strtolower($key) === 'content-type') { + $executionResponse['headers'][$key] = 'text/html'; } } } diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml index 48133087fd..d60b81f36c 100644 --- a/app/views/general/error.phtml +++ b/app/views/general/error.phtml @@ -25,6 +25,34 @@ if($exception !== null && method_exists($exception, 'getCTAs')) { } switch ($type) { + case 'empty_proxy_error': + $type = ''; + $label = 'Error ' . $code; + + $message = $code >= 500 ? 'An unexpected server error occured.' : 'An unexpected client error occured.'; + switch($code) { + case 401: + $message = 'You must sign in to access this page.'; + break; + case 403: + $message = 'You are not authorized to access this page.'; + break; + case 404: + $message = 'The page you are looking for does not exist.'; + break; + case 504: + $message = 'The server did not respond in time.'; + break; + case 501: + $message = 'This page is not implemented yet.'; + break; + } + + break; + case 'function_execute_permission_missing': + $label = 'Execution not permitted'; + $labelClass = 'warning'; + break; case 'build_not_ready': $label = 'Deployment is still building'; $message = 'The page will update after the build completes.'; @@ -40,7 +68,7 @@ switch ($type) { $message = 'This page is empty, but you can make it yours.'; break; case 'deployment_not_found': - $label = 'No deployments available'; + $label = 'No active deployments'; $message = 'This page is empty, activate a deployment to make it live.'; break; case 'build_canceled': @@ -246,7 +274,7 @@ switch ($type) { } .back-button { - margin-bottom: 24px; + margin-bottom: 12px; display: flex; align-items: center; gap: 8px; @@ -258,6 +286,10 @@ switch ($type) { line-height: 140%; letter-spacing: -0.45px; } + + .back-button:hover { + text-decoration: underline; + } .trace-grid { display: grid; @@ -412,9 +444,11 @@ switch ($type) {
print($label); ?>

print($message); ?>

-
- print($type); ?> -
+ +
+ print($type); ?> +
+
diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index c07b83a7e7..f2de2c7737 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2189,7 +2189,8 @@ class FunctionsCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('This page is empty, but you can make it yours.', $response['body']); + $this->assertStringContainsString('Nothing is here yet', $response['body']); + $this->assertStringContainsString('Start with this domain', $response['body']); // failed deployment $functionId = $this->setupFunction([ @@ -2198,7 +2199,7 @@ class FunctionsCustomServerTest extends Scope 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', 'timeout' => 15, - 'commands' => 'cd random', + 'commands' => 'cd non-existing-directory', 'execute' => ['any'] ]); @@ -2219,7 +2220,8 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); // canceled deployment $deployment = $this->createDeployment($functionId, [ @@ -2241,29 +2243,32 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); $this->cleanupFunction($functionId); + } - // no execute permission + public function testErrorPagesPermissions(): void + { $functionId = $this->setupFunction([ 'functionId' => ID::unique(), 'name' => 'Test Error Pages', 'runtime' => 'php-8.0', 'entrypoint' => 'index.php', - 'execute' => [], 'timeout' => 15, + 'commands' => '', + 'execute' => ['users'] ]); $domain = $this->setupFunctionDomain($functionId); + $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); $deploymentId = $this->setupDeployment($functionId, [ - 'entrypoint' => 'index.php', 'code' => $this->packageFunction('php'), 'activate' => true ]); - $this->assertNotEmpty($deploymentId); $response = $proxyClient->call(Client::METHOD_GET, '/', array_merge([ @@ -2272,7 +2277,79 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals(401, $response['headers']['status-code']); - $this->assertStringContainsString('FUNCTION_EXECUTE_PERMISSION_MISSING', $response['body']); + $this->assertStringContainsString('Execution not permitted', $response['body']); + $this->assertStringContainsString('View settings', $response['body']); + + $this->cleanupFunction($functionId); + } + + public function testErrorPagesEmptyBody(): void + { + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'name' => 'Test Error Pages', + 'runtime' => 'php-8.0', + 'entrypoint' => 'index.php', + 'timeout' => 15, + 'commands' => '', + 'execute' => ['any'] + ]); + + $domain = $this->setupFunctionDomain($functionId); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $deploymentId = $this->setupDeployment($functionId, [ + 'code' => $this->packageFunction('php'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=404', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(404, $response['headers']['status-code']); + $this->assertStringContainsString('Error 404', $response['body']); + $this->assertStringContainsString('does not exist', $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=504', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(504, $response['headers']['status-code']); + $this->assertStringContainsString('Error 504', $response['body']); + $this->assertStringContainsString('respond in time', $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=400', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('Error 400', $response['body']); + $this->assertStringContainsString('unexpected client error', $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=500', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(500, $response['headers']['status-code']); + $this->assertStringContainsString('Error 500', $response['body']); + $this->assertStringContainsString('unexpected server error', $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=400&body=CustomError400', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(400, $response['headers']['status-code']); + $this->assertStringContainsString('CustomError400', $response['body']); + + $response = $proxyClient->call(Client::METHOD_GET, '/custom-response?code=500&body=CustomError500', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ])); + $this->assertEquals(500, $response['headers']['status-code']); + $this->assertStringContainsString('CustomError500', $response['body']); $this->cleanupFunction($functionId); } diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index df6abfc833..e5f88461b2 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -2435,7 +2435,7 @@ class SitesCustomServerTest extends Scope }, 100000, 500); $response = $proxyClient->call(Client::METHOD_GET, '/'); - $this->assertStringContainsString('his page is empty, activate a deployment to make it live.', $response['body']); + $this->assertStringContainsString('This page is empty, activate a deployment to make it live.', $response['body']); $this->cleanupSite($siteId); } @@ -2515,16 +2515,16 @@ class SitesCustomServerTest extends Scope $response = $proxyClient->call(Client::METHOD_GET, '/'); $this->assertEquals(404, $response['headers']['status-code']); - $this->assertStringContainsString("This page is empty, but you can make it yours.", $response['body']); + $this->assertStringContainsString('Nothing is here yet', $response['body']); + $this->assertStringContainsString('Start with this domain', $response['body']); $siteId = $this->setupSite([ 'siteId' => ID::unique(), - 'name' => 'Astro SSR site', - 'framework' => 'astro', + 'name' => 'Static site', + 'framework' => 'other', 'buildRuntime' => 'node-22', - 'outputDirectory' => './dist', - 'buildCommand' => 'cd random', - 'installCommand' => 'npm install', + 'outputDirectory' => './', + 'buildCommand' => 'sleep 5 && cd non-existing-directory', ]); $this->assertNotEmpty($siteId); @@ -2532,29 +2532,20 @@ class SitesCustomServerTest extends Scope // test canceled deployment error page $deployment = $this->createDeployment($siteId, [ - 'code' => $this->packageSite('astro'), + 'code' => $this->packageSite('static'), 'activate' => 'true' ]); - $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); $this->assertNotEmpty($deployment['body']['$id']); - $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); - - $deploymentDomain = $this->getDeploymentDomain($deploymentId); - $this->assertNotEmpty($deploymentDomain); - - $this->assertEventually(function () use ($siteId, $deploymentId) { - $deployment = $this->getDeployment($siteId, $deploymentId); - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('building', $deployment['body']['status']); - }, 100000, 250); $deployment = $this->cancelDeployment($siteId, $deploymentId); $this->assertEquals(200, $deployment['headers']['status-code']); $this->assertEquals('canceled', $deployment['body']['status']); + $deploymentDomain = $this->getDeploymentDomain($deploymentId); + $this->assertNotEmpty($deploymentDomain); + $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $deploymentDomain); $response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false); @@ -2571,12 +2562,14 @@ class SitesCustomServerTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); $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 deployments available", $response['body']); + $this->assertStringContainsString('No active deployments', $response['body']); + $this->assertStringContainsString('View deployments', $response['body']); $deployment = $this->createDeployment($siteId, [ 'code' => $this->packageSite('astro'), @@ -2599,6 +2592,8 @@ class SitesCustomServerTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); $this->assertStringContainsString("Deployment is still building", $response['body']); + $this->assertStringContainsString("View logs", $response['body']); + $this->assertStringContainsString("Reload", $response['body']); $this->assertEventually(function () use ($siteId, $deploymentId) { $deployment = $this->getDeployment($siteId, $deploymentId); @@ -2612,6 +2607,7 @@ class SitesCustomServerTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); $this->assertStringContainsString("Deployment build failed", $response['body']); + $this->assertStringContainsString("View logs", $response['body']); $this->cleanupSite($siteId); } diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php index 27a9418b3c..6c67f27ee1 100644 --- a/tests/resources/functions/php/index.php +++ b/tests/resources/functions/php/index.php @@ -1,6 +1,12 @@ req->path === '/custom-response') { + $code = (int) ($context->req->query['code'] ?? '200'); + $body = $context->req->query['body'] ?? ''; + return $context->res->send($body, $code); + } + $context->log('body-is-' . ($context->req->body ?? '')); $context->log('custom-header-is-' . ($context->req->headers['x-custom-header'] ?? '')); $context->log('method-is-' . \strtolower($context->req->method ?? '')); diff --git a/ a.txt b/ a.txt new file mode 100644 index 0000000000..c62bf8784d --- /dev/null +++ b/ a.txt @@ -0,0 +1,8 @@ +PHPUnit 9.6.22 by Sebastian Bergmann and contributors. + +Tests\E2E\Services\Sites\SitesCustomServerTest::testErrorPages ended in 7904.421256 milliseconds +. 1 / 1 (100%) + +Time: 00:07.907, Memory: 30.16 MB + +OK (1 test, 67 assertions)