appwrite/tests/e2e/Scopes/Scope.php
Jake Barnby 6f56c9c2ee fix: Gate spatial index null tests on adapter capability
PostgreSQL supports spatial indexes on nullable columns and required
spatial attributes on existing data, unlike MariaDB. Expose
supportForSpatialIndexNull console variable and condition test
assertions accordingly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 06:22:53 +13:00

333 lines
9.7 KiB
PHP

<?php
namespace Tests\E2E\Scopes;
use Appwrite\Tests\Async;
use Appwrite\Tests\Retryable;
use PHPUnit\Framework\TestCase;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
abstract class Scope extends TestCase
{
use Retryable;
use Async;
public const REQUEST_TYPE_WEBHOOK = 'webhook';
public const REQUEST_TYPE_SMS = 'sms';
protected ?Client $client = null;
protected string $endpoint = 'http://appwrite/v1';
protected string $webEndpoint = 'http://appwrite.test/v1';
protected function setUp(): void
{
$this->client = new Client();
$this->client->setEndpoint($this->endpoint);
$format = System::getEnv('_APP_E2E_RESPONSE_FORMAT');
if (!empty($format)) {
if (
!\preg_match('/^\d+\.\d+\.\d+$/', $format) ||
!\version_compare($format, APP_VERSION_STABLE, '<=')
) {
throw new \Exception('E2E response format must be ' . APP_VERSION_STABLE . ' or lower.');
}
$this->client->setResponseFormat($format);
}
}
protected function tearDown(): void
{
$this->client = null;
}
/**
* @var array|null Cached console variables
*/
protected static ?array $consoleVariables = null;
/**
* Fetch console variables from the API
*/
protected function getConsoleVariables(): array
{
if (self::$consoleVariables !== null) {
return self::$consoleVariables;
}
$root = $this->getRoot();
$response = $this->client->call(Client::METHOD_GET, '/console/variables', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
'cookie' => 'a_session_console=' . $root['session'],
]);
self::$consoleVariables = $response['body'] ?? [];
return self::$consoleVariables;
}
/**
* Check if the database adapter supports relationships
*/
protected function getSupportForRelationships(): bool
{
return $this->getConsoleVariables()['supportForRelationships'] ?? true;
}
/**
* Check if the database adapter supports operators
*/
protected function getSupportForOperators(): bool
{
return $this->getConsoleVariables()['supportForOperators'] ?? true;
}
/**
* Check if the database adapter supports spatial attributes
*/
protected function getSupportForSpatials(): bool
{
return $this->getConsoleVariables()['supportForSpatials'] ?? true;
}
/**
* Check if the database adapter supports spatial indexes on nullable columns
*/
protected function getSupportForSpatialIndexNull(): bool
{
return $this->getConsoleVariables()['supportForSpatialIndexNull'] ?? false;
}
/**
* Check if the database adapter supports fulltext wildcard search
*/
protected function getSupportForFulltextWildcard(): bool
{
return $this->getConsoleVariables()['supportForFulltextWildcard'] ?? true;
}
/**
* Get the maximum index length supported by the database adapter
*/
protected function getMaxIndexLength(): int
{
return $this->getConsoleVariables()['maxIndexLength'] ?? 768;
}
protected function getLastEmail(int $limit = 1): array
{
sleep(3);
$emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
if ($emails && is_array($emails)) {
if ($limit === 1) {
return end($emails);
} else {
return array_slice($emails, -1 * $limit);
}
}
return [];
}
protected function extractQueryParamsFromEmailLink(string $html): array
{
foreach (['/join-us?', '/verification?', '/recovery?'] as $prefix) {
$linkStart = strpos($html, $prefix);
if ($linkStart !== false) {
$hrefStart = strrpos(substr($html, 0, $linkStart), 'href="');
if ($hrefStart === false) {
continue;
}
$hrefStart += 6;
$hrefEnd = strpos($html, '"', $hrefStart);
if ($hrefEnd === false || $hrefStart >= $hrefEnd) {
continue;
}
$link = substr($html, $hrefStart, $hrefEnd - $hrefStart);
$link = strtok($link, '#'); // Remove `#title`
$queryStart = strpos($link, '?');
if ($queryStart === false) {
continue;
}
$queryString = substr($link, $queryStart + 1);
parse_str(html_entity_decode($queryString), $queryParams);
return $queryParams;
}
}
return [];
}
protected function assertLastRequest(callable $probe, string $type, $timeoutMs = 20_000, $waitMs = 500): array
{
$hostname = match ($type) {
'webhook' => 'request-catcher-webhook',
'sms' => 'request-catcher-sms',
default => throw new \Exception('Invalid request catcher type.'),
};
$this->assertEventually(function () use (&$request, $probe, $hostname) {
$request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true);
$request['data'] = json_decode($request['data'], true);
call_user_func($probe, $request);
}, $timeoutMs, $waitMs);
return $request;
}
protected function assertSamePixels(string $expectedImagePath, string $actualImageBlob): void
{
$expected = new \Imagick($expectedImagePath);
$actual = new \Imagick();
$actual->readImageBlob($actualImageBlob);
foreach ([$expected, $actual] as $image) {
$image->setImageFormat('PNG');
$image->stripImage();
$image->setOption('png:exclude-chunks', 'date,time,iCCP,sRGB,gAMA,cHRM');
}
$this->assertSame($expected->getImageSignature(), $actual->getImageSignature());
}
/**
* @deprecated Use assertLastRequest instead. Used only historically in webhook tests
*/
protected function getLastRequest(): array
{
$hostname = 'request-catcher-webhook';
sleep(2);
$request = json_decode(file_get_contents('http://' . $hostname . ':5000/__last_request__'), true);
$request['data'] = json_decode($request['data'], true);
return $request;
}
/**
* @return array
*/
abstract public function getHeaders(bool $devKey = true): array;
/**
* @return array
*/
abstract public function getProject(): array;
/**
* @var array
*/
protected static $root = [];
/**
* @return array
*/
public function getRoot(): array
{
if ((self::$root)) {
return self::$root;
}
$email = uniqid() . 'user@localhost.test';
$password = 'password';
$name = 'User Name';
$root = $this->client->call(Client::METHOD_POST, '/account', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
], [
'userId' => ID::unique(),
'email' => $email,
'password' => $password,
'name' => $name,
]);
$this->assertEquals(201, $root['headers']['status-code']);
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => 'console',
], [
'email' => $email,
'password' => $password,
]);
$session = $session['cookies']['a_session_console'];
self::$root = [
'$id' => ID::custom($root['body']['$id']),
'name' => $root['body']['name'],
'email' => $root['body']['email'],
'session' => $session,
];
return self::$root;
}
/**
* @var array
*/
protected static $user = [];
/**
* @return array
*/
public function getUser(bool $fresh = false): array
{
if (!$fresh && isset(self::$user[$this->getProject()['$id']])) {
return self::$user[$this->getProject()['$id']];
}
$email = uniqid() . 'user@localhost.test';
$password = 'password';
$name = 'User Name';
$user = $this->client->call(Client::METHOD_POST, '/account', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'userId' => ID::unique(),
'email' => $email,
'password' => $password,
'name' => $name,
]);
$this->assertEquals(201, $user['headers']['status-code']);
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'email' => $email,
'password' => $password,
]);
$token = $session['cookies']['a_session_' . $this->getProject()['$id']];
self::$user[$this->getProject()['$id']] = [
'$id' => ID::custom($user['body']['$id']),
'name' => $user['body']['name'],
'email' => $user['body']['email'],
'session' => $token,
'sessionId' => $session['body']['$id'],
];
return self::$user[$this->getProject()['$id']];
}
}