Merge pull request #3699 from appwrite/feat-list-user-logs-queries
Update List User Logs API to Accept Queries
This commit is contained in:
commit
ad417731ce
24 changed files with 1372 additions and 79 deletions
|
@ -25,7 +25,6 @@ use Utopia\Database\Adapter\MariaDB;
|
|||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Key;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\Query as QueryValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Database\Exception\Authorization as AuthorizationException;
|
||||
|
@ -38,8 +37,12 @@ use Appwrite\Network\Validator\Email;
|
|||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries as QueriesValidator;
|
||||
use Appwrite\Utopia\Database\Validator\OrderAttributes;
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor as CursorQueryValidator;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter as FilterQueryValidator;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit as LimitQueryValidator;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset as OffsetQueryValidator;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order as OrderQueryValidator;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Detector\Detector;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
|
@ -2028,15 +2031,42 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
$filterQueries = \array_map(function ($query) {
|
||||
$query = Query::parse($query);
|
||||
|
||||
if (\count($query->getValues()) > 100) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_LIMIT_EXCEEDED, "You cannot use more than 100 query values on attribute '{$query->getAttribute()}'");
|
||||
if (!empty($queries)) {
|
||||
$attributes = array_merge(
|
||||
$collection->getAttribute('attributes', []),
|
||||
[
|
||||
new Document([
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]),
|
||||
new Document([
|
||||
'key' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]),
|
||||
new Document([
|
||||
'key' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]),
|
||||
]
|
||||
);
|
||||
$validator = new IndexedQueries(
|
||||
$attributes,
|
||||
$collection->getAttribute('indexes', []),
|
||||
new CursorQueryValidator(),
|
||||
new FilterQueryValidator($attributes),
|
||||
new LimitQueryValidator(),
|
||||
new OffsetQueryValidator(),
|
||||
new OrderQueryValidator(),
|
||||
);
|
||||
if (!$validator->isValid($queries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}, $queries);
|
||||
$filterQueries = Query::parseQueries($queries);
|
||||
|
||||
$otherQueries = [];
|
||||
$otherQueries[] = Query::limit($limit);
|
||||
|
@ -2060,14 +2090,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
|
||||
$allQueries = \array_merge($filterQueries, $otherQueries);
|
||||
|
||||
if (!empty($allQueries)) {
|
||||
$attributes = $collection->getAttribute('attributes', []);
|
||||
$validator = new QueriesValidator(new QueryValidator($attributes), $attributes, $collection->getAttribute('indexes', []), true);
|
||||
if (!$validator->isValid($allQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
if ($documentSecurity) {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $allQueries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
|
|
|
@ -9,7 +9,10 @@ use Appwrite\Event\Event;
|
|||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Stats\Stats;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Audit\Audit;
|
||||
|
@ -29,7 +32,6 @@ use Utopia\Database\Validator\Authorization;
|
|||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Boolean;
|
||||
use MaxMind\Db\Reader;
|
||||
use Utopia\Validator\Integer;
|
||||
|
@ -573,14 +575,13 @@ App::get('/v1/users/:userId/logs')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_LOG_LIST)
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('limit', 25, new Range(0, 100), 'Maximum number of logs to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', 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/databases#querying-documents). Only supported methods are limit and offset', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('usage')
|
||||
->action(function (string $userId, int $limit, int $offset, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Stats $usage) {
|
||||
->action(function (string $userId, array $queries, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Stats $usage) {
|
||||
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
|
@ -588,6 +589,11 @@ App::get('/v1/users/:userId/logs')
|
|||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? 25;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
||||
$audit = new Audit($dbForProject);
|
||||
|
||||
$logs = $audit->getLogsByUser($user->getId(), $limit, $offset);
|
||||
|
|
16
composer.lock
generated
16
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "5d5b72c7940a8376e187cce1b33d327a",
|
||||
"content-hash": "0e850206a924d2a48861ecd290f59bc0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -3526,23 +3526,23 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.15",
|
||||
"version": "9.2.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
|
||||
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"nikic/php-parser": "^4.13.0",
|
||||
"nikic/php-parser": "^4.14",
|
||||
"php": ">=7.3",
|
||||
"phpunit/php-file-iterator": "^3.0.3",
|
||||
"phpunit/php-text-template": "^2.0.2",
|
||||
|
@ -3591,7 +3591,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3599,7 +3599,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-07T09:28:20+00:00"
|
||||
"time": "2022-08-20T05:26:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
|
153
src/Appwrite/Utopia/Database/Validator/IndexedQueries.php
Normal file
153
src/Appwrite/Utopia/Database/Validator/IndexedQueries.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class IndexedQueries extends Queries
|
||||
{
|
||||
/**
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $attributes = [];
|
||||
|
||||
/**
|
||||
* @var Document[]
|
||||
*/
|
||||
protected $indexes = [];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* This Queries Validator filters indexes for only available indexes
|
||||
*
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param Base ...$validators
|
||||
* @param bool $strict
|
||||
*/
|
||||
public function __construct($attributes = [], $indexes = [], Base ...$validators)
|
||||
{
|
||||
$this->attributes = $attributes;
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_UNIQUE,
|
||||
'attributes' => ['$id']
|
||||
]);
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['$createdAt']
|
||||
]);
|
||||
|
||||
$this->indexes[] = new Document([
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['$updatedAt']
|
||||
]);
|
||||
|
||||
foreach ($indexes ?? [] as $index) {
|
||||
$this->indexes[] = $index;
|
||||
}
|
||||
|
||||
parent::__construct(...$validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if indexed array $indexes matches $queries
|
||||
*
|
||||
* @param array $indexes
|
||||
* @param array $queries
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function arrayMatch(array $indexes, array $queries): bool
|
||||
{
|
||||
// Check the count of indexes first for performance
|
||||
if (count($queries) !== count($indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort them for comparison, the order is not important here anymore.
|
||||
sort($indexes, SORT_STRING);
|
||||
sort($queries, SORT_STRING);
|
||||
|
||||
// Only matching arrays will have equal diffs in both directions
|
||||
if (array_diff_assoc($indexes, $queries) !== array_diff_assoc($queries, $indexes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns false if:
|
||||
* 1. any query in $value is invalid based on $validator
|
||||
* 2. there is no index with an exact match of the filters
|
||||
* 3. there is no index with an exact match of the order attributes
|
||||
*
|
||||
* Otherwise, returns true.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!parent::isValid($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
foreach ($value as $query) {
|
||||
if (!$query instanceof Query) {
|
||||
$query = Query::parse($query);
|
||||
}
|
||||
|
||||
$queries[] = $query;
|
||||
}
|
||||
|
||||
$grouped = Query::groupByType($queries);
|
||||
/** @var Query[] */ $filters = $grouped['filters'];
|
||||
/** @var string[] */ $orderAttributes = $grouped['orderAttributes'];
|
||||
|
||||
// Check filter queries for exact index match
|
||||
if (count($filters) > 0) {
|
||||
$filtersByAttribute = [];
|
||||
foreach ($filters as $filter) {
|
||||
$filtersByAttribute[$filter->getAttribute()] = $filter->getMethod();
|
||||
}
|
||||
|
||||
$found = null;
|
||||
|
||||
foreach ($this->indexes as $index) {
|
||||
if ($this->arrayMatch($index->getAttribute('attributes'), array_keys($filtersByAttribute))) {
|
||||
$found = $index;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
$this->message = 'Index not found: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
|
||||
// search method requires fulltext index
|
||||
if (in_array(Query::TYPE_SEARCH, array_values($filtersByAttribute)) && $found['type'] !== Database::INDEX_FULLTEXT) {
|
||||
$this->message = 'Search method requires fulltext index: ' . implode(",", array_keys($filtersByAttribute));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check order attributes for exact index match
|
||||
$validator = new OrderAttributes($this->attributes, $this->indexes, true);
|
||||
if (count($orderAttributes) > 0 && !$validator->isValid($orderAttributes)) {
|
||||
$this->message = $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -2,28 +2,139 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Queries as ValidatorQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Queries extends ValidatorQueries
|
||||
class Queries extends Validator
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
* This Queries Validator that filters indexes for only available indexes
|
||||
*
|
||||
* @param QueryValidator $validator
|
||||
* @param Document[] $attributes
|
||||
* @param Document[] $indexes
|
||||
* @param bool $strict
|
||||
* @var string
|
||||
*/
|
||||
public function __construct($validator, $attributes = [], $indexes = [], $strict = true)
|
||||
{
|
||||
// Remove failed/stuck/processing indexes
|
||||
$availableIndexes = \array_filter($indexes, function ($index) {
|
||||
return $index->getAttribute('status') === 'available';
|
||||
});
|
||||
protected $message = 'Invalid queries';
|
||||
|
||||
parent::__construct($validator, $attributes, $availableIndexes, $strict);
|
||||
/**
|
||||
* @var Base[]
|
||||
*/
|
||||
protected $validators;
|
||||
|
||||
/**
|
||||
* Queries constructor
|
||||
*
|
||||
* @param Base ...$validators a list of validators
|
||||
*/
|
||||
public function __construct(Base ...$validators)
|
||||
{
|
||||
$this->validators = $validators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns false if:
|
||||
* 1. any query in $value is invalid based on $validator
|
||||
*
|
||||
* Otherwise, returns true.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
foreach ($value as $query) {
|
||||
if (!$query instanceof Query) {
|
||||
try {
|
||||
$query = Query::parse($query);
|
||||
} catch (\Throwable $th) {
|
||||
$this->message = 'Invalid query: ${query}';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$method = $query->getMethod();
|
||||
$methodType = '';
|
||||
switch ($method) {
|
||||
case Query::TYPE_LIMIT:
|
||||
$methodType = Base::METHOD_TYPE_LIMIT;
|
||||
break;
|
||||
case Query::TYPE_OFFSET:
|
||||
$methodType = Base::METHOD_TYPE_OFFSET;
|
||||
break;
|
||||
case Query::TYPE_CURSORAFTER:
|
||||
case Query::TYPE_CURSORBEFORE:
|
||||
$methodType = Base::METHOD_TYPE_CURSOR;
|
||||
break;
|
||||
case Query::TYPE_ORDERASC:
|
||||
case Query::TYPE_ORDERDESC:
|
||||
$methodType = Base::METHOD_TYPE_ORDER;
|
||||
break;
|
||||
case Query::TYPE_EQUAL:
|
||||
case Query::TYPE_NOTEQUAL:
|
||||
case Query::TYPE_LESSER:
|
||||
case Query::TYPE_LESSEREQUAL:
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
$methodType = Base::METHOD_TYPE_FILTER;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$methodIsValid = false;
|
||||
foreach ($this->validators as $validator) {
|
||||
if ($validator->getMethodType() !== $methodType) {
|
||||
continue;
|
||||
}
|
||||
if (!$validator->isValid($query)) {
|
||||
$this->message = 'Query not valid: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
$methodIsValid = true;
|
||||
}
|
||||
|
||||
if (!$methodIsValid) {
|
||||
$this->message = 'Query method not valid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Queries as QueriesValidator;
|
||||
use Utopia\Database\Validator\Query as QueryValidator;
|
||||
|
||||
class Collection extends QueriesValidator
|
||||
class Collection extends IndexedQueries
|
||||
{
|
||||
/**
|
||||
* Expression constructor
|
||||
|
@ -39,6 +43,22 @@ class Collection extends QueriesValidator
|
|||
]);
|
||||
}
|
||||
|
||||
$attributes[] = new Document([
|
||||
'key' => '$id',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new Document([
|
||||
'$id' => '$createdAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
$attributes[] = new Document([
|
||||
'$id' => '$updatedAt',
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'array' => false,
|
||||
]);
|
||||
|
||||
$indexes = [];
|
||||
foreach ($allowedAttributes as $attribute) {
|
||||
$indexes[] = new Document([
|
||||
|
@ -53,6 +73,14 @@ class Collection extends QueriesValidator
|
|||
'attributes' => ['search']
|
||||
]);
|
||||
|
||||
parent::__construct(new QueryValidator($attributes), $attributes, $indexes, true);
|
||||
$validators = [
|
||||
new Limit(),
|
||||
new Offset(),
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Order($attributes),
|
||||
];
|
||||
|
||||
parent::__construct($attributes, $indexes, ...$validators);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ class Users extends Collection
|
|||
'registration',
|
||||
'emailVerification',
|
||||
'phoneVerification',
|
||||
'search',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator;
|
||||
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Database\Validator\Query as QueryValidator;
|
||||
|
||||
class Query extends QueryValidator
|
||||
{
|
||||
protected function isValidCursor($cursor): bool
|
||||
{
|
||||
$validator = new UID();
|
||||
|
||||
if ($validator->isValid($cursor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->message = 'Invalid cursor: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
}
|
70
src/Appwrite/Utopia/Database/Validator/Query/Base.php
Normal file
70
src/Appwrite/Utopia/Database/Validator/Query/Base.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Utopia\Validator;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
abstract class Base extends Validator
|
||||
{
|
||||
public const METHOD_TYPE_LIMIT = 'limit';
|
||||
public const METHOD_TYPE_OFFSET = 'offset';
|
||||
public const METHOD_TYPE_CURSOR = 'cursor';
|
||||
public const METHOD_TYPE_ORDER = 'order';
|
||||
public const METHOD_TYPE_FILTER = 'filter';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Invalid query';
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_OBJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function isValid($query): bool;
|
||||
|
||||
/**
|
||||
* Returns what type of query this Validator is for
|
||||
*/
|
||||
abstract public function getMethodType(): string;
|
||||
}
|
44
src/Appwrite/Utopia/Database/Validator/Query/Cursor.php
Normal file
44
src/Appwrite/Utopia/Database/Validator/Query/Cursor.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\UID;
|
||||
|
||||
class Cursor extends Base
|
||||
{
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is cursorBefore or cursorAfter and value is not null
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method === Query::TYPE_CURSORAFTER || $method === Query::TYPE_CURSORBEFORE) {
|
||||
$cursor = $query->getValue();
|
||||
$validator = new UID();
|
||||
if ($validator->isValid($cursor)) {
|
||||
return true;
|
||||
}
|
||||
$this->message = 'Invalid cursor: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_CURSOR;
|
||||
}
|
||||
}
|
114
src/Appwrite/Utopia/Database/Validator/Query/Filter.php
Normal file
114
src/Appwrite/Utopia/Database/Validator/Query/Filter.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Filter extends Base
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message = 'Invalid query';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [];
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxValuesCount
|
||||
*/
|
||||
public function __construct(array $attributes = [], int $maxValuesCount = 100)
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
|
||||
}
|
||||
|
||||
$this->maxValuesCount = $maxValuesCount;
|
||||
}
|
||||
|
||||
protected function isValidAttribute($attribute): bool
|
||||
{
|
||||
// Search for attribute in schema
|
||||
if (!isset($this->schema[$attribute])) {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function isValidAttributeAndValues(string $attribute, array $values): bool
|
||||
{
|
||||
if (!$this->isValidAttribute($attribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$attributeSchema = $this->schema[$attribute];
|
||||
|
||||
if (count($values) > $this->maxValuesCount) {
|
||||
$this->message = 'Query on attribute has greater than ' . $this->maxValuesCount . ' values: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the type of desired attribute from collection $schema
|
||||
$attributeType = $attributeSchema['type'];
|
||||
|
||||
foreach ($values as $value) {
|
||||
$condition = match ($attributeType) {
|
||||
Database::VAR_DATETIME => gettype($value) === Database::VAR_STRING,
|
||||
default => gettype($value) === $attributeType
|
||||
};
|
||||
|
||||
if (!$condition) {
|
||||
$this->message = 'Query type does not match expected: ' . $attributeType;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is a filter method, attribute exists, and value matches attribute type
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
$attribute = $query->getAttribute();
|
||||
|
||||
switch ($method) {
|
||||
case Query::TYPE_EQUAL:
|
||||
case Query::TYPE_NOTEQUAL:
|
||||
case Query::TYPE_LESSER:
|
||||
case Query::TYPE_LESSEREQUAL:
|
||||
case Query::TYPE_GREATER:
|
||||
case Query::TYPE_GREATEREQUAL:
|
||||
case Query::TYPE_SEARCH:
|
||||
$values = $query->getValues();
|
||||
return $this->isValidAttributeAndValues($attribute, $values);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_FILTER;
|
||||
}
|
||||
}
|
61
src/Appwrite/Utopia/Database/Validator/Query/Limit.php
Normal file
61
src/Appwrite/Utopia/Database/Validator/Query/Limit.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Limit extends Base
|
||||
{
|
||||
protected int $maxLimit;
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxLimit
|
||||
*/
|
||||
public function __construct(int $maxLimit = 100)
|
||||
{
|
||||
$this->maxLimit = $maxLimit;
|
||||
}
|
||||
|
||||
protected function isValidLimit($limit): bool
|
||||
{
|
||||
$validator = new Range(0, $this->maxLimit);
|
||||
if ($validator->isValid($limit)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->message = 'Invalid limit: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is limit values are within range.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method !== Query::TYPE_LIMIT) {
|
||||
$this->message = 'Query method invalid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
|
||||
$limit = $query->getValue();
|
||||
return $this->isValidLimit($limit);
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_LIMIT;
|
||||
}
|
||||
}
|
61
src/Appwrite/Utopia/Database/Validator/Query/Offset.php
Normal file
61
src/Appwrite/Utopia/Database/Validator/Query/Offset.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator\Range;
|
||||
|
||||
class Offset extends Base
|
||||
{
|
||||
protected int $maxOffset;
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
* @param int $maxOffset
|
||||
*/
|
||||
public function __construct(int $maxOffset = 5000)
|
||||
{
|
||||
$this->maxOffset = $maxOffset;
|
||||
}
|
||||
|
||||
protected function isValidOffset($offset): bool
|
||||
{
|
||||
$validator = new Range(0, $this->maxOffset);
|
||||
if ($validator->isValid($offset)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->message = 'Invalid offset: ' . $validator->getDescription();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is offset and values are within range.
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
// Validate method
|
||||
$method = $query->getMethod();
|
||||
|
||||
if ($method !== Query::TYPE_OFFSET) {
|
||||
$this->message = 'Query method invalid: ' . $method;
|
||||
return false;
|
||||
}
|
||||
|
||||
$offset = $query->getValue();
|
||||
return $this->isValidOffset($offset);
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_OFFSET;
|
||||
}
|
||||
}
|
68
src/Appwrite/Utopia/Database/Validator/Query/Order.php
Normal file
68
src/Appwrite/Utopia/Database/Validator/Query/Order.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Order extends Base
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [];
|
||||
|
||||
/**
|
||||
* Query constructor
|
||||
*
|
||||
*/
|
||||
public function __construct(array $attributes = [])
|
||||
{
|
||||
foreach ($attributes as $attribute) {
|
||||
$this->schema[$attribute->getAttribute('key')] = $attribute->getArrayCopy();
|
||||
}
|
||||
}
|
||||
|
||||
protected function isValidAttribute($attribute): bool
|
||||
{
|
||||
// Search for attribute in schema
|
||||
if (!isset($this->schema[$attribute])) {
|
||||
$this->message = 'Attribute not found in schema: ' . $attribute;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if method is ORDER_ASC or ORDER_DESC and attributes are valid
|
||||
*
|
||||
* Otherwise, returns false
|
||||
*
|
||||
* @param Query $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($query): bool
|
||||
{
|
||||
$method = $query->getMethod();
|
||||
$attribute = $query->getAttribute();
|
||||
|
||||
if ($method === Query::TYPE_ORDERASC || $method === Query::TYPE_ORDERDESC) {
|
||||
if ($attribute === '') {
|
||||
return true;
|
||||
}
|
||||
return $this->isValidAttribute($attribute);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getMethodType(): string
|
||||
{
|
||||
return self::METHOD_TYPE_ORDER;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
namespace Tests\E2E\Services\Users;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
trait UsersBase
|
||||
|
@ -997,7 +996,7 @@ trait UsersBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(1)']
|
||||
'offset' => 1
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
|
@ -1008,16 +1007,80 @@ trait UsersBase
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
'offset(1)',
|
||||
'limit(1)',
|
||||
]
|
||||
'offset' => 1,
|
||||
'limit' => 1
|
||||
]);
|
||||
|
||||
$this->assertEquals($logs['headers']['status-code'], 200);
|
||||
$this->assertIsArray($logs['body']['logs']);
|
||||
$this->assertLessThanOrEqual(1, count($logs['body']['logs']));
|
||||
$this->assertIsNumeric($logs['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['limit(101)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(-1)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['offset(5001)']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['equal("$id", "asdf")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['orderAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/logs', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => ['cursorAsc("$id")']
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 400);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
121
tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php
Normal file
121
tests/unit/Utopia/Database/Validator/IndexedQueriesTest.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\IndexedQueries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class IndexedQueriesTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testEmptyQueries(): void
|
||||
{
|
||||
$validator = new IndexedQueries();
|
||||
|
||||
$this->assertEquals(true, $validator->isValid([]));
|
||||
}
|
||||
|
||||
public function testInvalidQuery(): void
|
||||
{
|
||||
$validator = new IndexedQueries();
|
||||
|
||||
$this->assertEquals(false, $validator->isValid(["this.is.invalid"]));
|
||||
}
|
||||
|
||||
public function testInvalidMethod(): void
|
||||
{
|
||||
$validator = new IndexedQueries();
|
||||
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
|
||||
|
||||
$validator = new IndexedQueries([], [], new Limit());
|
||||
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
|
||||
}
|
||||
|
||||
public function testInvalidValue(): void
|
||||
{
|
||||
$validator = new IndexedQueries([], [], new Limit());
|
||||
$this->assertEquals(false, $validator->isValid(['limit(-1)']));
|
||||
}
|
||||
|
||||
public function testValid(): void
|
||||
{
|
||||
$attributes = [
|
||||
new Document([
|
||||
'key' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]),
|
||||
];
|
||||
$indexes = [
|
||||
new Document([
|
||||
'status' => 'available',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
]),
|
||||
new Document([
|
||||
'status' => 'available',
|
||||
'type' => Database::INDEX_FULLTEXT,
|
||||
'attributes' => ['name'],
|
||||
]),
|
||||
];
|
||||
$validator = new IndexedQueries(
|
||||
$attributes,
|
||||
$indexes,
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Limit(),
|
||||
new Offset(),
|
||||
new Order($attributes),
|
||||
);
|
||||
$this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['search("name", "value")']), $validator->getDescription());
|
||||
}
|
||||
|
||||
public function testMissingIndex(): void
|
||||
{
|
||||
$attributes = [
|
||||
new Document([
|
||||
'key' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]),
|
||||
];
|
||||
$indexes = [
|
||||
new Document([
|
||||
'status' => 'available',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['name'],
|
||||
]),
|
||||
];
|
||||
$validator = new IndexedQueries(
|
||||
$attributes,
|
||||
$indexes,
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Limit(),
|
||||
new Offset(),
|
||||
new Order($attributes),
|
||||
);
|
||||
$this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription());
|
||||
$this->assertEquals(false, $validator->isValid(['search("name", "value")']), $validator->getDescription());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Collection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CollectionTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testEmptyQueries(): void
|
||||
{
|
||||
$validator = new Collection('users', []);
|
||||
|
||||
$this->assertEquals($validator->isValid([]), true);
|
||||
}
|
||||
|
||||
public function testValid(): void
|
||||
{
|
||||
$validator = new Collection('users', ['name', 'search']);
|
||||
$this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription());
|
||||
}
|
||||
|
||||
public function testMissingIndex(): void
|
||||
{
|
||||
$validator = new Collection('users', ['name']);
|
||||
$this->assertEquals(false, $validator->isValid(['equal("dne", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(false, $validator->isValid(['orderAsc("dne")']), $validator->getDescription());
|
||||
$this->assertEquals(false, $validator->isValid(['search("search", "value")']), $validator->getDescription());
|
||||
}
|
||||
}
|
40
tests/unit/Utopia/Database/Validator/Queries/UsersTest.php
Normal file
40
tests/unit/Utopia/Database/Validator/Queries/UsersTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Queries;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Users;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class UsersTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testIsValid(): void
|
||||
{
|
||||
$validator = new Users();
|
||||
|
||||
/**
|
||||
* Test for Success
|
||||
*/
|
||||
$this->assertEquals(true, $validator->isValid([]), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("email", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("phone", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['greaterThan("passwordUpdate", "2020-10-15 06:38")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['greaterThan("registration", "2020-10-15 06:38")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("emailVerification", true)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("phoneVerification", true)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['search("search", "value")']), $validator->getDescription());
|
||||
|
||||
/**
|
||||
* Test for Failure
|
||||
*/
|
||||
$this->assertEquals(false, $validator->isValid(['equal("password", "value")']), $validator->getDescription());
|
||||
}
|
||||
}
|
76
tests/unit/Utopia/Database/Validator/QueriesTest.php
Normal file
76
tests/unit/Utopia/Database/Validator/QueriesTest.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Queries;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class QueriesTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testEmptyQueries(): void
|
||||
{
|
||||
$validator = new Queries();
|
||||
|
||||
$this->assertEquals(true, $validator->isValid([]));
|
||||
}
|
||||
|
||||
public function testInvalidQuery(): void
|
||||
{
|
||||
$validator = new Queries();
|
||||
|
||||
$this->assertEquals(false, $validator->isValid(["this.is.invalid"]));
|
||||
}
|
||||
|
||||
public function testInvalidMethod(): void
|
||||
{
|
||||
$validator = new Queries();
|
||||
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
|
||||
|
||||
$validator = new Queries(new Limit());
|
||||
$this->assertEquals(false, $validator->isValid(['equal("attr", "value")']));
|
||||
}
|
||||
|
||||
public function testInvalidValue(): void
|
||||
{
|
||||
$validator = new Queries(new Limit());
|
||||
$this->assertEquals(false, $validator->isValid(['limit(-1)']));
|
||||
}
|
||||
|
||||
public function testValid(): void
|
||||
{
|
||||
$attributes = [
|
||||
new Document([
|
||||
'key' => 'name',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
])
|
||||
];
|
||||
$validator = new Queries(
|
||||
new Cursor(),
|
||||
new Filter($attributes),
|
||||
new Limit(),
|
||||
new Offset(),
|
||||
new Order($attributes),
|
||||
);
|
||||
$this->assertEquals(true, $validator->isValid(['cursorAfter("asdf")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['equal("name", "value")']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['limit(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['offset(10)']), $validator->getDescription());
|
||||
$this->assertEquals(true, $validator->isValid(['orderAsc("name")']), $validator->getDescription());
|
||||
}
|
||||
}
|
41
tests/unit/Utopia/Database/Validator/Query/CursorTest.php
Normal file
41
tests/unit/Utopia/Database/Validator/Query/CursorTest.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CursorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Base
|
||||
*/
|
||||
protected $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Cursor();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValue(): void
|
||||
{
|
||||
// Test for Success
|
||||
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), true, $this->validator->getDescription());
|
||||
|
||||
// Test for Failure
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
59
tests/unit/Utopia/Database/Validator/Query/FilterTest.php
Normal file
59
tests/unit/Utopia/Database/Validator/Query/FilterTest.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Filter;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FilterTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Base
|
||||
*/
|
||||
protected $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Filter(
|
||||
attributes: [
|
||||
new Document([
|
||||
'key' => 'attr',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValue(): void
|
||||
{
|
||||
// Test for Success
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), true, $this->validator->getDescription());
|
||||
|
||||
// Test for Failure
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(0)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(0)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5000)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORAFTER, values: ['asdf'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(new Query(Query::TYPE_CURSORBEFORE, values: ['asdf'])), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
37
tests/unit/Utopia/Database/Validator/Query/LimitTest.php
Normal file
37
tests/unit/Utopia/Database/Validator/Query/LimitTest.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class LimitTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Base
|
||||
*/
|
||||
protected $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Limit();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValue(): void
|
||||
{
|
||||
// Test for Success
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(1)), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(0)), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(100)), true, $this->validator->getDescription());
|
||||
|
||||
// Test for Failure
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
41
tests/unit/Utopia/Database/Validator/Query/OffsetTest.php
Normal file
41
tests/unit/Utopia/Database/Validator/Query/OffsetTest.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OffsetTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Base
|
||||
*/
|
||||
protected $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Offset();
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValue(): void
|
||||
{
|
||||
// Test for Success
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(1)), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(0)), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5000)), true, $this->validator->getDescription());
|
||||
|
||||
// Test for Failure
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(100)), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
54
tests/unit/Utopia/Database/Validator/Query/OrderTest.php
Normal file
54
tests/unit/Utopia/Database/Validator/Query/OrderTest.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Utopia\Database\Validator\Query;
|
||||
|
||||
use Appwrite\Utopia\Database\Validator\Query\Base;
|
||||
use Appwrite\Utopia\Database\Validator\Query\Order;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OrderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var Base
|
||||
*/
|
||||
protected $validator = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->validator = new Order(
|
||||
attributes: [
|
||||
new Document([
|
||||
'key' => 'attr',
|
||||
'type' => Database::VAR_STRING,
|
||||
'array' => false,
|
||||
]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testValue(): void
|
||||
{
|
||||
// Test for Success
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('attr')), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderAsc('')), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('attr')), true, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('')), true, $this->validator->getDescription());
|
||||
|
||||
// Test for Failure
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::limit(101)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(-1)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::offset(5001)), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('attr', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('dne', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::equal('', ['v'])), false, $this->validator->getDescription());
|
||||
$this->assertEquals($this->validator->isValid(Query::orderDesc('dne')), false, $this->validator->getDescription());
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue