2025-01-01 20:51:30 +00:00
< ? php
namespace Tests\E2E\Services\Sites ;
use Appwrite\Tests\Async ;
2025-08-16 14:59:05 +00:00
use Appwrite\Tests\Async\Exceptions\Critical ;
2025-01-01 20:51:30 +00:00
use CURLFile ;
use Tests\E2E\Client ;
2026-02-10 05:04:24 +00:00
use Utopia\Console ;
2025-02-12 18:28:25 +00:00
use Utopia\Database\Helpers\ID ;
2025-02-08 11:11:09 +00:00
use Utopia\Database\Query ;
2025-02-12 18:28:25 +00:00
use Utopia\System\System ;
2025-01-01 20:51:30 +00:00
trait SitesBase
{
use Async ;
protected string $stdout = '' ;
protected string $stderr = '' ;
protected function setupSite ( mixed $params ) : string
{
$site = $this -> client -> call ( Client :: METHOD_POST , '/sites' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), $params );
$this -> assertEquals ( $site [ 'headers' ][ 'status-code' ], 201 , 'Setup site failed with status code: ' . $site [ 'headers' ][ 'status-code' ] . ' and response: ' . json_encode ( $site [ 'body' ], JSON_PRETTY_PRINT ));
$siteId = $site [ 'body' ][ '$id' ];
return $siteId ;
}
protected function setupDeployment ( string $siteId , mixed $params ) : string
{
$deployment = $this -> client -> call ( Client :: METHOD_POST , '/sites/' . $siteId . '/deployments' , array_merge ([
'content-type' => 'multipart/form-data' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]), $params );
$this -> assertEquals ( $deployment [ 'headers' ][ 'status-code' ], 202 , 'Setup deployment failed with status code: ' . $deployment [ 'headers' ][ 'status-code' ] . ' and response: ' . json_encode ( $deployment [ 'body' ], JSON_PRETTY_PRINT ));
$deploymentId = $deployment [ 'body' ][ '$id' ] ? ? '' ;
$this -> assertEventually ( function () use ( $siteId , $deploymentId ) {
$deployment = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/deployments/' . $deploymentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
2025-08-16 12:14:38 +00:00
if ( $deployment [ 'body' ][ 'status' ] === 'failed' ) {
2025-08-16 14:59:05 +00:00
throw new Critical ( 'Deployment failed: ' . json_encode ( $deployment [ 'body' ], JSON_PRETTY_PRINT ));
2025-08-16 12:14:38 +00:00
}
Console :: execute ( " docker inspect openruntimes-executor --format=' { { .State.ExitCode}}' " , '' , $this -> stdout , $this -> stderr );
2025-08-16 14:59:05 +00:00
if ( \trim ( $this -> stdout ) !== '0' ) {
2025-08-16 12:14:38 +00:00
$msg = 'Executor has a problem: ' . $this -> stderr . ' (' . $this -> stdout . '), current status: ' ;
Console :: execute ( " docker compose logs openruntimes-executor " , '' , $this -> stdout , $this -> stderr );
$msg .= $this -> stdout . ' (' . $this -> stderr . ')' ;
2025-08-16 14:59:05 +00:00
throw new Critical ( $msg . json_encode ( $deployment [ 'body' ], JSON_PRETTY_PRINT ));
2025-08-16 12:14:38 +00:00
}
2025-01-01 20:51:30 +00:00
$this -> assertEquals ( 'ready' , $deployment [ 'body' ][ 'status' ], 'Deployment status is not ready, deployment: ' . json_encode ( $deployment [ 'body' ], JSON_PRETTY_PRINT ));
2025-08-16 12:14:38 +00:00
}, 300000 , 500 );
2025-01-01 20:51:30 +00:00
2025-02-20 13:22:30 +00:00
// Not === so multipart/form-data works fine too
if (( $params [ 'activate' ] ? ? false ) == true ) {
$this -> assertEventually ( function () use ( $siteId , $deploymentId ) {
$site = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( $deploymentId , $site [ 'body' ][ 'deploymentId' ], 'Deployment is not activated, deployment: ' . json_encode ( $site [ 'body' ], JSON_PRETTY_PRINT ));
}, 100000 , 500 );
}
2025-01-01 20:51:30 +00:00
return $deploymentId ;
}
protected function cleanupSite ( string $siteId ) : void
{
$site = $this -> client -> call ( Client :: METHOD_DELETE , '/sites/' . $siteId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( $site [ 'headers' ][ 'status-code' ], 204 );
}
2025-01-06 21:03:32 +00:00
protected function cleanupDeployment ( string $siteId , string $deploymentId ) : void
{
$deployment = $this -> client -> call ( Client :: METHOD_DELETE , '/sites/' . $siteId . '/deployments/' . $deploymentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( $deployment [ 'headers' ][ 'status-code' ], 204 );
}
2025-01-01 20:51:30 +00:00
protected function createSite ( mixed $params ) : mixed
{
$site = $this -> client -> call ( Client :: METHOD_POST , '/sites' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $site ;
}
2025-01-06 21:03:32 +00:00
protected function updateSite ( mixed $params ) : mixed
{
$site = $this -> client -> call ( Client :: METHOD_PUT , '/sites/' . $params [ '$id' ], array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $site ;
}
2025-01-01 20:51:30 +00:00
protected function createVariable ( string $siteId , mixed $params ) : mixed
{
$variable = $this -> client -> call ( Client :: METHOD_POST , '/sites/' . $siteId . '/variables' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $variable ;
}
2025-02-07 17:57:46 +00:00
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 ;
}
2025-01-01 20:51:30 +00:00
protected function getSite ( string $siteId ) : mixed
{
$site = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $site ;
}
protected function getDeployment ( string $siteId , string $deploymentId ) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/deployments/' . $deploymentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $deployment ;
}
protected function getLog ( string $siteId , $logId ) : mixed
{
$log = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/logs/' . $logId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $log ;
}
protected function listSites ( mixed $params = []) : mixed
{
$sites = $this -> client -> call ( Client :: METHOD_GET , '/sites' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $sites ;
}
protected function listDeployments ( string $siteId , $params = []) : mixed
{
$deployments = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/deployments' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $deployments ;
}
2025-02-28 17:29:17 +00:00
protected function listLogs ( string $siteId , array $queries = []) : mixed
2025-01-01 20:51:30 +00:00
{
2025-01-08 08:25:21 +00:00
$logs = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/logs' , array_merge ([
2025-01-01 20:51:30 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-02-28 17:29:17 +00:00
], $this -> getHeaders ()), [
'queries' => $queries
]);
2025-01-01 20:51:30 +00:00
return $logs ;
}
protected function packageSite ( string $site ) : CURLFile
{
$folderPath = realpath ( __DIR__ . '/../../../resources/sites' ) . " / $site " ;
$tarPath = " $folderPath /code.tar.gz " ;
Console :: execute ( " cd $folderPath && tar --exclude code.tar.gz -czf code.tar.gz . " , '' , $this -> stdout , $this -> stderr );
if ( filesize ( $tarPath ) > 1024 * 1024 * 5 ) {
throw new \Exception ( 'Code package is too large. Use the chunked upload method instead.' );
}
return new CURLFile ( $tarPath , 'application/x-gzip' , \basename ( $tarPath ));
}
protected function createDeployment ( string $siteId , mixed $params = []) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_POST , '/sites/' . $siteId . '/deployments' , array_merge ([
'content-type' => 'multipart/form-data' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $deployment ;
}
2025-03-20 14:22:35 +00:00
protected function deleteDeployment ( string $siteId , string $deploymentId ) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_DELETE , '/sites/' . $siteId . '/deployments/' . $deploymentId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), []);
return $deployment ;
}
2025-03-05 11:29:27 +00:00
protected function setupDuplicateDeployment ( string $siteId , string $deploymentId ) : string
{
$deployment = $this -> createDuplicateDeployment ( $siteId , $deploymentId );
$this -> assertEquals ( 202 , $deployment [ 'headers' ][ 'status-code' ]);
$deploymentId = $deployment [ 'body' ][ '$id' ];
$this -> assertNotEmpty ( $deploymentId );
$this -> assertEventually ( function () use ( $siteId , $deploymentId ) {
$deployment = $this -> getDeployment ( $siteId , $deploymentId );
$this -> assertEquals ( 'ready' , $deployment [ 'body' ][ 'status' ], 'Deployment status is not ready, deployment: ' . json_encode ( $deployment [ 'body' ], JSON_PRETTY_PRINT ));
2025-06-02 16:15:01 +00:00
}, 150000 , 500 );
2025-03-05 11:29:27 +00:00
$this -> assertEventually ( function () use ( $siteId , $deploymentId ) {
$site = $this -> getSite ( $siteId );
$this -> assertEquals ( $deploymentId , $site [ 'body' ][ 'deploymentId' ], 'Deployment is not activated, deployment: ' . json_encode ( $site [ 'body' ], JSON_PRETTY_PRINT ));
}, 100000 , 500 );
return $deploymentId ;
}
protected function createDuplicateDeployment ( string $siteId , string $deploymentId ) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_POST , '/sites/' . $siteId . '/deployments/duplicate' , array_merge ([
'content-type' => 'multipart/form-data' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'deploymentId' => $deploymentId ,
]);
return $deployment ;
}
2025-02-14 23:36:46 +00:00
protected function createTemplateDeployment ( string $siteId , mixed $params = []) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_POST , '/sites/' . $siteId . '/deployments/template' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $deployment ;
}
2025-03-08 23:19:54 +00:00
protected function getUsage ( string $siteId , mixed $params ) : mixed
2025-01-01 20:51:30 +00:00
{
$usage = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/usage' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), $params );
return $usage ;
}
protected function getTemplate ( string $templateId )
{
2025-03-09 14:42:17 +00:00
$template = $this -> client -> call ( Client :: METHOD_GET , '/sites/templates/' . $templateId , [
2025-03-09 14:33:06 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-03-09 14:42:17 +00:00
]);
2025-09-18 07:42:46 +00:00
2025-01-01 20:51:30 +00:00
return $template ;
}
2025-09-19 12:48:44 +00:00
protected function helperGetLatestCommit ( string $owner , string $repository ) : ? string
{
2026-02-11 07:39:21 +00:00
$maxRetries = 3 ;
for ( $attempt = 0 ; $attempt < $maxRetries ; $attempt ++ ) {
if ( $attempt > 0 ) {
sleep ( 5 );
}
2025-09-19 12:48:44 +00:00
2026-02-11 07:39:21 +00:00
$ch = curl_init ( " https://api.github.com/repos/ { $owner } / { $repository } /commits/main " );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
curl_setopt ( $ch , CURLOPT_HTTPHEADER , [
'User-Agent: Appwrite' ,
'Accept: application/vnd.github.v3+json'
]);
$response = curl_exec ( $ch );
$httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
curl_close ( $ch );
if ( $httpCode === 200 ) {
$commitData = json_decode ( $response , true );
if ( isset ( $commitData [ 'sha' ])) {
return $commitData [ 'sha' ];
}
2025-09-18 07:42:46 +00:00
}
}
2025-09-19 12:48:44 +00:00
return null ;
2025-01-01 20:51:30 +00:00
}
protected function deleteSite ( string $siteId ) : mixed
{
$site = $this -> client -> call ( Client :: METHOD_DELETE , '/sites/' . $siteId , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $site ;
}
2025-02-08 11:11:09 +00:00
2025-02-18 06:42:12 +00:00
protected function setupSiteDomain ( string $siteId , string $subdomain = '' ) : string
2025-02-12 18:28:25 +00:00
{
2026-01-30 15:20:46 +00:00
$sitesDomain = \explode ( ',' , System :: getEnv ( '_APP_DOMAIN_SITES' , '' ))[ 0 ];
2025-02-12 18:28:25 +00:00
$subdomain = $subdomain ? $subdomain : ID :: unique ();
2025-02-22 17:56:51 +00:00
$rule = $this -> client -> call ( Client :: METHOD_POST , '/proxy/rules/site' , array_merge ([
2025-02-12 18:28:25 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
2026-01-30 15:20:46 +00:00
'domain' => $subdomain . '.' . $sitesDomain ,
2025-02-22 17:56:51 +00:00
'siteId' => $siteId ,
2025-02-12 18:28:25 +00:00
]);
$this -> assertEquals ( 201 , $rule [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $rule [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $rule [ 'body' ][ 'domain' ]);
$domain = $rule [ 'body' ][ 'domain' ];
return $domain ;
}
2025-02-08 11:11:09 +00:00
protected function getSiteDomain ( string $siteId ) : string
{
$rules = $this -> client -> call ( Client :: METHOD_GET , '/proxy/rules' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [
2025-03-07 09:43:54 +00:00
Query :: equal ( 'deploymentResourceId' , [ $siteId ]) -> toString (),
2025-03-08 18:19:05 +00:00
Query :: equal ( 'trigger' , [ 'manual' ]) -> toString (),
2025-02-23 20:34:14 +00:00
Query :: equal ( 'type' , [ 'deployment' ]) -> toString (),
2025-02-08 11:11:09 +00:00
],
]);
$this -> assertEquals ( 200 , $rules [ 'headers' ][ 'status-code' ]);
$this -> assertGreaterThanOrEqual ( 1 , $rules [ 'body' ][ 'total' ]);
$this -> assertGreaterThanOrEqual ( 1 , \count ( $rules [ 'body' ][ 'rules' ]));
$this -> assertNotEmpty ( $rules [ 'body' ][ 'rules' ][ 0 ][ 'domain' ]);
$domain = $rules [ 'body' ][ 'rules' ][ 0 ][ 'domain' ];
return $domain ;
}
2025-02-08 14:53:55 +00:00
protected function getDeploymentDomain ( string $deploymentId ) : string
{
$rules = $this -> client -> call ( Client :: METHOD_GET , '/proxy/rules' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'queries' => [
2025-03-07 09:43:54 +00:00
Query :: equal ( 'deploymentId' , [ $deploymentId ]) -> toString (),
2025-02-23 20:34:14 +00:00
Query :: equal ( 'type' , [ 'deployment' ]) -> toString (),
2025-03-08 18:19:05 +00:00
Query :: equal ( 'trigger' , [ 'deployment' ]) -> toString (),
2025-02-08 14:53:55 +00:00
],
]);
$this -> assertEquals ( 200 , $rules [ 'headers' ][ 'status-code' ]);
$this -> assertGreaterThanOrEqual ( 1 , $rules [ 'body' ][ 'total' ]);
$this -> assertGreaterThanOrEqual ( 1 , \count ( $rules [ 'body' ][ 'rules' ]));
$this -> assertNotEmpty ( $rules [ 'body' ][ 'rules' ][ 0 ][ 'domain' ]);
$domain = $rules [ 'body' ][ 'rules' ][ 0 ][ 'domain' ];
return $domain ;
}
2025-02-28 11:04:17 +00:00
2025-03-04 12:45:44 +00:00
protected function getDeploymentDownload ( string $siteId , string $deploymentId , string $type ) : mixed
2025-02-28 11:04:17 +00:00
{
$response = $this -> client -> call ( Client :: METHOD_GET , '/sites/' . $siteId . '/deployments/' . $deploymentId . '/download' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-03-04 12:45:44 +00:00
], $this -> getHeaders ()), [
'type' => $type ,
]);
2025-02-28 11:04:17 +00:00
return $response ;
}
2025-03-04 15:04:37 +00:00
2025-03-04 15:12:57 +00:00
protected function updateSiteDeployment ( string $siteId , string $deploymentId ) : mixed
2025-03-04 15:04:37 +00:00
{
$site = $this -> client -> call ( Client :: METHOD_PATCH , '/sites/' . $siteId . '/deployment' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()), [
'deploymentId' => $deploymentId
]);
return $site ;
}
2025-03-06 11:20:08 +00:00
protected function cancelDeployment ( string $siteId , string $deploymentId ) : mixed
{
$deployment = $this -> client -> call ( Client :: METHOD_PATCH , '/sites/' . $siteId . '/deployments/' . $deploymentId . '/status' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $deployment ;
}
2025-03-07 22:17:15 +00:00
protected function listSpecifications () : mixed
{
$specifications = $this -> client -> call ( Client :: METHOD_GET , '/sites/specifications' , array_merge ([
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], $this -> getHeaders ()));
return $specifications ;
}
2025-01-01 20:51:30 +00:00
}