Files
appwrite/tests/e2e/Scopes/Scope.php
T
Jake BarnbyandClaude Opus 4.6 9b2dd958b5 fix: Use correct capability check for partial fulltext search in Users test
PostgreSQL's ts_vector/ts_query doesn't support partial word matching
(searching "man" won't find "manchester"), unlike MariaDB's LIKE-based
search. The test used getSupportForRelationships() as a proxy for this
capability, which was wrong for PostgreSQL (supports relationships but
not partial fulltext search).

Add supportForFulltextWildcard to console variables endpoint and use
it in the Users test instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 04:56:39 +13:00

326 lines
9.4 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 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']];
}
}