1
0
Fork 0
mirror of synced 2024-06-02 10:54:44 +12:00

Merge branch 'feat-functions-refactor' of https://github.com/appwrite/appwrite into feat-func-storage-sync

This commit is contained in:
Damodar Lohani 2022-02-18 11:32:37 +00:00
commit 5eae00df2c
27 changed files with 301 additions and 114 deletions

View file

@ -1541,6 +1541,17 @@ $collections = [
'array' => false,
'filters' => ['encrypt'],
],
[
'$id' => 'search',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@ -1564,6 +1575,13 @@ $collections = [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_search',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [],
'orders' => [],
],
],
],

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -144,7 +144,7 @@ App::get('/v1/avatars/image')
->label('sdk.description', '/docs/references/avatars/get-image.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->param('url', '', new URL(), 'Image URL which you want to crop.')
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true)
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000.', true)
->inject('response')
@ -213,7 +213,7 @@ App::get('/v1/avatars/favicon')
->label('sdk.description', '/docs/references/avatars/get-favicon.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_IMAGE)
->param('url', '', new URL(), 'Website URL which you want to fetch the favicon from.')
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
->inject('response')
->action(function ($url, $response) {
/** @var Appwrite\Utopia\Response $response */

View file

@ -585,7 +585,7 @@ App::post('/v1/projects/:projectId/webhooks')
->param('projectId', null, new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
->param('url', null, new URL(), 'Webhook URL.')
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)
@ -707,7 +707,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId')
->param('webhookId', null, new UID(), 'Webhook unique ID.')
->param('name', null, new Text(128), 'Webhook name. Max length: 128 chars.')
->param('events', null, new ArrayList(new WhiteList(array_keys(Config::getParam('events'), true), true)), 'Events list.')
->param('url', null, new URL(), 'Webhook URL.')
->param('url', null, new URL(['http', 'https']), 'Webhook URL.')
->param('security', false, new Boolean(true), 'Certificate verification, false for disabled or true for enabled.')
->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true)
->param('httpPass', '', new Text(256), 'Webhook HTTP password. Max length: 256 chars.', true)

View file

@ -63,7 +63,9 @@ App::post('/v1/teams')
])));
if (!$isPrivilegedUser && !$isAppUser) { // Don't add user on server mode
$membershipId = $dbForProject->getId();
$membership = new Document([
'$id' => $membershipId,
'$read' => ['user:'.$user->getId(), 'team:'.$team->getId()],
'$write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'],
'userId' => $user->getId(),
@ -73,6 +75,7 @@ App::post('/v1/teams')
'joined' => \time(),
'confirm' => true,
'secret' => '',
'search' => implode(' ', [$membershipId, $user->getId()])
]);
$membership = $dbForProject->createDocument('memberships', $membership);
@ -353,8 +356,9 @@ App::post('/v1/teams/:teamId/memberships')
$secret = Auth::tokenGenerator();
$membershipId = $dbForProject->getId();
$membership = new Document([
'$id' => $dbForProject->getId(),
'$id' => $membershipId,
'$read' => ['role:all'],
'$write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'],
'userId' => $invitee->getId(),
@ -364,6 +368,7 @@ App::post('/v1/teams/:teamId/memberships')
'joined' => ($isPrivilegedUser || $isAppUser) ? \time() : 0,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
]);
if ($isPrivilegedUser || $isAppUser) { // Allow admin to create membership
@ -458,8 +463,27 @@ App::get('/v1/teams/:teamId/memberships')
}
}
$memberships = $dbForProject->find('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], $limit, $offset, [], [$orderType], $cursorMembership ?? null, $cursorDirection);
$sum = $dbForProject->count('memberships', [new Query('teamId', Query::TYPE_EQUAL, [$teamId])], APP_LIMIT_COUNT);
$queries = [new Query('teamId', Query::TYPE_EQUAL, [$teamId])];
if (!empty($search)) {
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$memberships = $dbForProject->find(
collection: 'memberships',
queries: $queries,
limit: $limit,
offset: $offset,
orderTypes: [$orderType],
cursor: $cursorMembership ?? null,
cursorDirection: $cursorDirection
);
$sum = $dbForProject->count(
collection:'memberships',
queries: $queries,
max: APP_LIMIT_COUNT
);
$memberships = array_filter($memberships, fn(Document $membership) => !empty($membership->getAttribute('userId')));
@ -565,25 +589,40 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
$isOwner = Authorization::isRole('team:'.$team->getId().'/owner');;
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');;
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception('User is not allowed to modify roles', 401);
}
// Update the roles
/**
* Update the roles
*/
$membership->setAttribute('roles', $roles);
$membership = $dbForProject->updateDocument('memberships', $membership->getId(), $membership);
// TODO sync updated membership in the user $profile object using TYPE_REPLACE
/**
* Replace membership on profile
*/
$memberships = array_filter($profile->getAttribute('memberships'), fn (Document $m) => $m->getId() !== $membership->getId());
$profile
->setAttribute('memberships', $memberships)
->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND);
Authorization::skip(fn () => $dbForProject->updateDocument('users', $profile->getId(), $profile));
$audits
->setParam('userId', $user->getId())
->setParam('event', 'teams.memberships.update')
->setParam('resource', 'team/'.$teamId)
;
->setParam('resource', 'team/' . $teamId);
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
$response->dynamic(
$membership
->setAttribute('email', $profile->getAttribute('email'))
->setAttribute('name', $profile->getAttribute('name')),
Response::MODEL_MEMBERSHIP
);
});
App::patch('/v1/teams/:teamId/memberships/:membershipId/status')

View file

@ -21,6 +21,7 @@ use Utopia\Storage\Device\S3;
use Utopia\Storage\Storage;
use Utopia\Swoole\Request;
use Utopia\Swoole\Response;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Range as ValidatorRange;
use Utopia\Validator\Text;
@ -142,13 +143,14 @@ App::post('/v1/runtimes')
->param('runtimeId', '', new Text(62), 'Unique runtime ID.')
->param('source', '', new Text(0), 'Path to source files.')
->param('destination', '', new Text(0), 'Destination folder to store build files into.')
->param('vars', '', new Assoc(), 'Environment Variables required for the build')
->param('vars', [], new Assoc(), 'Environment Variables required for the build')
->param('commands', [], new ArrayList(new Text(0)), 'Commands required to build the container')
->param('runtime', '', new Text(128), 'Runtime for the cloud function')
->param('baseImage', '', new Text(128), 'Base image name of the runtime')
->inject('orchestrationPool')
->inject('activeRuntimes')
->inject('response')
->action(function (string $runtimeId, string $source, string $destination, array $vars, string $runtime, string $baseImage, $orchestrationPool, $activeRuntimes, Response $response) {
->action(function (string $runtimeId, string $source, string $destination, array $vars, array $commands, string $runtime, string $baseImage, $orchestrationPool, $activeRuntimes, Response $response) {
$container = 'r-' . $runtimeId;
@ -197,7 +199,7 @@ App::post('/v1/runtimes')
$container = 'b-' . $runtimeId;
$vars = array_map(fn ($v) => strval($v), $vars);
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 0))
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 1))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', 256))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
@ -231,61 +233,21 @@ App::post('/v1/runtimes')
/**
* Extract user code into build container
*/
$untarStdout = '';
$untarStderr = '';
$untarSuccess = $orchestration->execute(
$status = $orchestration->execute(
name: $container,
command: [
'sh',
'-c',
'mkdir -p /usr/code && cp /tmp/code.tar.gz /usr/workspace/code.tar.gz && cd /usr/workspace/ && tar -zxf /usr/workspace/code.tar.gz -C /usr/code && rm /usr/workspace/code.tar.gz'
],
stdout: $untarStdout,
stderr: $untarStderr,
timeout: 60
);
if (!$untarSuccess) {
throw new Exception('Failed to extract tarfile ' . $untarStderr, 500);
}
/**
* Build code and install dependenices
*/
$buildSuccess = $orchestration->execute(
name: $container,
command: ['sh', '-c', 'cd /usr/local/src && ./build.sh'],
command: $commands,
stdout: $buildStdout,
stderr: $buildStderr,
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
timeout: App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)
);
if (!$buildSuccess) {
throw new Exception('Failed to build dependencies: ' . $buildStderr, 500);
}
/**
* Repackage code and save
*/
$compressStdout = '';
$compressStderr = '';
$compressSuccess = $orchestration->execute(
name: $container,
command: [
'tar', '-C', '/usr/code', '-czvf', '/usr/builds/code.tar.gz', './'
],
stdout: $compressStdout,
stderr: $compressStderr,
timeout: 60
);
if (!$compressSuccess) {
throw new Exception('Failed to compress built code: ' . $compressStderr, 500);
if (!$status) {
throw new Exception('Failed to build dependenices ' . $buildStderr, 500);
}
// Check if the build was successful by checking if file exists
if (!\file_exists($tmpBuild)) {
throw new Exception('Something went wrong during the build process');
throw new Exception('Something went wrong during the build process', 500);
}
/**
@ -311,8 +273,8 @@ App::post('/v1/runtimes')
$build = [
'outputPath' => $outputPath,
'status' => 'ready',
'stdout' => \utf8_encode(\mb_substr($buildStdout, -4096)),
'stderr' => \utf8_encode(\mb_substr($buildStderr, -4096)),
'stdout' => \utf8_encode($buildStdout),
'stderr' => \utf8_encode($buildStderr),
'startTime' => $buildStart,
'endTime' => $buildEnd,
'duration' => $buildEnd - $buildStart,
@ -356,9 +318,9 @@ App::post('/v1/runtimes')
$vars = array_map(fn ($v) => strval($v), $vars);
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', '1'))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', '256'))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', '256'));
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 1))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', 256))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
$id = $orchestration->run(
image: $baseImage,
@ -487,7 +449,7 @@ App::post('/v1/execution')
->desc('Create an execution')
->param('runtimeId', '', new Text(62), 'The runtimeID to execute')
->param('path', '', new Text(0), 'Path containing the built files.', false)
->param('vars', '', new Assoc(), 'Environment variables required for the build', false)
->param('vars', [], new Assoc(), 'Environment variables required for the build', false)
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
->param('runtime', '', new Text(128), 'Runtime for the cloud function', false)
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file')

View file

@ -119,7 +119,18 @@ class BuildsV1 extends Worker
source: $source,
vars: $vars,
runtime: $key,
baseImage: $baseImage
baseImage: $baseImage,
commands: [
'sh', '-c',
'mkdir -p /usr/code && \
cp /tmp/code.tar.gz /usr/workspace/code.tar.gz && \
cd /usr/workspace/ && \
tar -zxf /usr/workspace/code.tar.gz -C /usr/code && \
rm /usr/workspace/code.tar.gz && \
cd /usr/local/src && \
./build.sh && \
tar -C /usr/code -czf /usr/builds/code.tar.gz ./'
]
);
/** Update the build document */

View file

@ -54,7 +54,7 @@
"utopia-php/storage": "0.7.*",
"utopia-php/websocket": "0.1.0",
"utopia-php/image": "0.5.*",
"utopia-php/orchestration": "0.3.*",
"utopia-php/orchestration": "0.4.*",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "5.0.1",
"dragonmantank/cron-expression": "3.1.0",

38
composer.lock generated
View file

@ -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": "be98fbe479fad1146fb54411ebfc9a57",
"content-hash": "70ce232aec13929c9cc7901fa34282a0",
"packages": [
{
"name": "adhocore/jwt",
@ -119,11 +119,11 @@
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "85053974a0d17415c0bd955ac8d6758c0d186d2b"
"reference": "ea44eb19af851a3fe4ecf73e789f374f70d778c5"
},
"require": {
"php": ">=8.0",
"utopia-php/orchestration": "0.3.x",
"utopia-php/orchestration": "0.4.*",
"utopia-php/system": "0.4.*"
},
"require-dev": {
@ -160,7 +160,7 @@
"php",
"runtimes"
],
"time": "2022-02-09T12:31:08+00:00"
"time": "2022-02-17T12:06:12+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -2470,16 +2470,16 @@
},
{
"name": "utopia-php/orchestration",
"version": "0.3.0",
"version": "0.4.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/orchestration.git",
"reference": "5398361b39fdb7a15b561ebf243c4cb75a68432a"
"reference": "6f25a47e6d6b14540853b62db43c676aecf80519"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/5398361b39fdb7a15b561ebf243c4cb75a68432a",
"reference": "5398361b39fdb7a15b561ebf243c4cb75a68432a",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/6f25a47e6d6b14540853b62db43c676aecf80519",
"reference": "6f25a47e6d6b14540853b62db43c676aecf80519",
"shasum": ""
},
"require": {
@ -2519,9 +2519,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/orchestration/issues",
"source": "https://github.com/utopia-php/orchestration/tree/0.3.0"
"source": "https://github.com/utopia-php/orchestration/tree/0.4.0"
},
"time": "2021-12-20T14:08:08+00:00"
"time": "2022-02-17T11:48:37+00:00"
},
{
"name": "utopia-php/preloader",
@ -3077,11 +3077,17 @@
},
{
"name": "appwrite/sdk-generator",
"version": "dev-feat-preps-for-0.13",
"version": "0.17.2",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator",
"reference": "b977fcf357a267f41299539ac9095aa7bbcc2600"
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "37bc6fc1b4b4940c7659748d7d2d5110da5457a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/37bc6fc1b4b4940c7659748d7d2d5110da5457a4",
"reference": "37bc6fc1b4b4940c7659748d7d2d5110da5457a4",
"shasum": ""
},
"require": {
"ext-curl": "*",
@ -3116,7 +3122,11 @@
}
],
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"time": "2022-02-15T11:09:40+00:00"
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/0.17.2"
},
"time": "2022-01-28T08:25:10+00:00"
},
{
"name": "composer/pcre",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -764,6 +764,25 @@
'content-type': 'application/json',
}, payload);
}),
/**
* Update Session (Refresh Tokens)
*
*
* @param {string} sessionId
* @throws {AppwriteException}
* @returns {Promise}
*/
updateSession: (sessionId) => __awaiter(this, void 0, void 0, function* () {
if (typeof sessionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "sessionId"');
}
let path = '/account/sessions/{sessionId}'.replace('{sessionId}', sessionId);
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('patch', uri, {
'content-type': 'application/json',
}, payload);
}),
/**
* Delete Account Session
*
@ -1463,9 +1482,9 @@
* @param {string} collectionId
* @param {string} key
* @param {boolean} required
* @param {string} min
* @param {string} max
* @param {string} xdefault
* @param {number} min
* @param {number} max
* @param {number} xdefault
* @param {boolean} array
* @throws {AppwriteException}
* @returns {Promise}

View file

@ -290,7 +290,7 @@ class Realtime extends Adapter
$channels[] = 'documents';
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents';
$channels[] = 'documents.' . $payload->getId();
$channels[] = 'collections.' . $payload->getAttribute('$collection') . '.documents.' . $payload->getId();
$roles = ($collection->getAttribute('permission') === 'collection') ? $collection->getRead() : $payload->getRead();

View file

@ -9,10 +9,20 @@ use Utopia\Validator;
*
* Validate that an variable is a valid URL
*
* @package Utopia\Validator
* @package Appwrite\Network\Validator
*/
class URL extends Validator
{
protected array $allowedSchemes;
/**
* @param array $allowedSchemes
*/
public function __construct(array $allowedSchemes = [])
{
$this->allowedSchemes = $allowedSchemes;
}
/**
* Get Description
*
@ -22,6 +32,10 @@ class URL extends Validator
*/
public function getDescription(): string
{
if (!empty($this->allowedSchemes)) {
return 'Value must be a valid URL with following schemes (' . \implode(', ', $this->allowedSchemes) . ')';
}
return 'Value must be a valid URL';
}
@ -39,6 +53,10 @@ class URL extends Validator
return false;
}
if (!empty($this->allowedSchemes) && !\in_array(\parse_url($value, PHP_URL_SCHEME), $this->allowedSchemes)) {
return false;
}
return true;
}

View file

@ -21,9 +21,9 @@ class Func extends Model
->addRule('execute', [
'type' => self::TYPE_STRING,
'description' => 'Execution permissions.',
'default' => '',
'default' => [],
'example' => 'role:member',
'array' => false,
'array' => true,
])
->addRule('name', [
'type' => self::TYPE_STRING,

View file

@ -37,8 +37,9 @@ class Executor
string $source,
array $vars,
string $runtime,
string $baseImage)
{
string $baseImage,
array $commands
) {
$route = "/runtimes";
$headers = [
'content-type' => 'application/json',
@ -51,7 +52,8 @@ class Executor
'destination' => APP_STORAGE_BUILDS . "/app-$projectId",
'vars' => $vars,
'runtime' => $runtime,
'baseImage' => $baseImage
'baseImage' => $baseImage,
'commands' => $commands
];
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, 30);

View file

@ -259,6 +259,14 @@ trait AvatarsBase
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/avatars/image', [
'x-appwrite-project' => $this->getProject()['$id'],
], [
'url' => 'invalid://appwrite.io/images/apple.png'
]);
$this->assertEquals(400, $response['headers']['status-code']);
// TODO Add test for non-image file (PDF, WORD)
return [];

View file

@ -25,6 +25,7 @@ class FunctionsCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => 'unique()',
'execute' => ['role:all'],
'name' => 'Test',
'runtime' => 'php-8.0',
'vars' => [
@ -58,6 +59,9 @@ class FunctionsCustomServerTest extends Scope
'account.create',
'account.delete',
], $response1['body']['events']);
$this->assertEquals([
'role:all'
], $response1['body']['execute']);
$this->assertEquals('0 0 1 1 *', $response1['body']['schedule']);
$this->assertEquals(10, $response1['body']['timeout']);

View file

@ -834,6 +834,17 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Webhook Test',
'events' => ['account.create', 'account.update.email'],
'url' => 'invalid://appwrite.io',
]);
$this->assertEquals(400, $response['headers']['status-code']);
return $data;
}
@ -979,6 +990,17 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_PUT, '/projects/'.$id.'/webhooks/'.$webhookId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Webhook Test Update',
'events' => ['account.delete', 'account.sessions.delete', 'storage.files.create'],
'url' => 'invalid://appwrite.io/new',
]);
$this->assertEquals(400, $response['headers']['status-code']);
return $data;
}

View file

@ -80,8 +80,8 @@ class RealtimeCustomClientTest extends Scope
'collections.1.documents',
'collections.2.documents',
'documents',
'documents.1',
'documents.2',
'collections.1.documents.1',
'collections.2.documents.2',
], $headers);
$response = json_decode($client->receive(), true);
@ -100,8 +100,8 @@ class RealtimeCustomClientTest extends Scope
$this->assertContains('collections.1.documents', $response['data']['channels']);
$this->assertContains('collections.2.documents', $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.1', $response['data']['channels']);
$this->assertContains('documents.2', $response['data']['channels']);
$this->assertContains('collections.1.documents.1', $response['data']['channels']);
$this->assertContains('collections.2.documents.2', $response['data']['channels']);
$this->assertEquals($userId, $response['data']['user']['$id']);
$client->close();
@ -606,7 +606,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.create', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
@ -638,7 +638,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $data['documentId'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.update', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
@ -676,7 +676,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.delete', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
@ -767,7 +767,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $actors['body']['$id'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.create', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
@ -798,7 +798,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $data['documentId'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $data['documentId'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.update', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);
@ -836,7 +836,7 @@ class RealtimeCustomClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(3, $response['data']['channels']);
$this->assertContains('documents', $response['data']['channels']);
$this->assertContains('documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents.' . $document['body']['$id'], $response['data']['channels']);
$this->assertContains('collections.' . $data['actorsId'] . '.documents', $response['data']['channels']);
$this->assertEquals('database.documents.delete', $response['data']['event']);
$this->assertNotEmpty($response['data']['payload']);

View file

@ -28,6 +28,48 @@ trait TeamsBaseClient
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$membershipId = $response['body']['memberships'][0]['$id'];
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $this->getUser()['$id']
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['sum']);
$this->assertNotEmpty($response['body']['memberships'][0]);
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['name']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => $membershipId
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['sum']);
$this->assertNotEmpty($response['body']['memberships'][0]);
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['name']);
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['email']);
$this->assertEquals('owner', $response['body']['memberships'][0]['roles'][0]);
$response = $this->client->call(Client::METHOD_GET, '/teams/'.$teamUid.'/memberships', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'unknown'
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertIsInt($response['body']['sum']);
$this->assertEmpty($response['body']['memberships']);
$this->assertEquals(0, $response['body']['sum']);
/**
* Test for FAILURE
*/

View file

@ -17,10 +17,7 @@ use PHPUnit\Framework\TestCase;
class URLTest extends TestCase
{
/**
* @var Domain
*/
protected $url = null;
protected ?URL $url;
public function setUp():void
{
@ -32,9 +29,9 @@ class URLTest extends TestCase
$this->url = null;
}
public function testIsValid()
public function testIsValid(): void
{
// Assertions
$this->assertEquals('Value must be a valid URL', $this->url->getDescription());
$this->assertEquals(true, $this->url->isValid('http://example.com'));
$this->assertEquals(true, $this->url->isValid('https://example.com'));
$this->assertEquals(true, $this->url->isValid('htts://example.com')); // does not validate protocol
@ -45,4 +42,13 @@ class URLTest extends TestCase
$this->assertEquals(true, $this->url->isValid('http://www.example.com/foo%2\u00c2\u00a9zbar'));
$this->assertEquals(true, $this->url->isValid('http://www.example.com/?q=%3Casdf%3E'));
}
public function testIsValidAllowedSchemes(): void
{
$this->url = new URL(['http', 'https']);
$this->assertEquals('Value must be a valid URL with following schemes (http, https)', $this->url->getDescription());
$this->assertEquals(true, $this->url->isValid('http://example.com'));
$this->assertEquals(true, $this->url->isValid('https://example.com'));
$this->assertEquals(false, $this->url->isValid('gopher://www.example.com'));
}
}