1
0
Fork 0
mirror of synced 2024-06-26 18:20:43 +12:00

Merge branch 'master' into 0.13.x

This commit is contained in:
Damodar Lohani 2022-02-22 06:43:07 +00:00
commit 53de779e89
31 changed files with 243 additions and 75 deletions

View file

@ -1,3 +1,12 @@
# Version 0.12.3
## Bugs
- Fix update membership roles (#2799)
- Fix migration to 0.12.x to populate search fields (#2799)
## Security
- Fix URL schema Validation to only allow http/https (#2801)
# Version 0.12.2
## Bugs

View file

@ -59,7 +59,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
### Windows
@ -71,7 +71,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
#### PowerShell
@ -81,7 +81,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。

View file

@ -62,7 +62,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
### Windows
@ -74,7 +74,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
#### PowerShell
@ -84,7 +84,7 @@ docker run -it --rm ,
--volume /var/run/docker.sock:/var/run/docker.sock ,
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw ,
--entrypoint="install" ,
appwrite/appwrite:0.12.2
appwrite/appwrite:0.12.3
```
Once the Docker installation completes, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after installation completes.

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, Exception::USER_UNAUTHORIZED);
}
// 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

@ -70,7 +70,7 @@ const APP_LIMIT_ENCRYPTION = 20000000; //20MB
const APP_LIMIT_COMPRESSION = 20000000; //20MB
const APP_LIMIT_PREVIEW = 10000000; //10MB file size limit for preview endpoint
const APP_CACHE_BUSTER = 201;
const APP_VERSION_STABLE = '0.13.0';
const APP_VERSION_STABLE = '0.12.3';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';

View file

@ -494,7 +494,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<input type="hidden" data-forms-key-value data-ls-attrs="name={{$index}}" data-ls-bind="{{var}}" />
</div>
<div class="col span-2">
<button type="button" data-remove class="reverse danger round pull-end"><i class="icon-cancel"></i></button>
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
</div>
</div>
</div>
@ -507,7 +507,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<input type="hidden" data-ls-attrs="data-forms-key-value"/>
</div>
<div class="col span-2">
<button type="button" data-remove class="reverse danger round pull-end"><i class="icon-cancel"></i></button>
<button type="button" data-remove class="close pull-end is-margin-top-10"><i class="icon-cancel"></i></button>
</div>
</div>
</div>

View file

@ -241,11 +241,6 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
</p>
<?php endif; ?>
</div>
<hr />
<div class="text-align-center text-size-small text-bold text-success" data-ls-if="!!({{console-project.serviceStatusFor<?php echo ucFirst($this->escape($key)); ?>}})">Enabled</div>
<div class="text-align-center text-size-small text-bold text-danger" data-ls-if="(!{{console-project.serviceStatusFor<?php echo ucFirst($this->escape($key)); ?>}})">Disabled</div>
</div>
</li>
<?php endforeach; ?>
@ -548,7 +543,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<div class="margin-bottom-tiny">
<span data-ls-bind="{{member.name}}"></span> &nbsp;&nbsp;<span class="tag" data-ls-bind="{{member.roles.0}}"></span> &nbsp;&nbsp;<span data-ls-if="false === {{member.confirm}}" class="tag red">Pending Approval</span>
</div>
<span class="text-size-small text-fade" data-ls-bind="{{member.email}}"></small>
<span class="text-size-small text-fade" data-ls-bind="{{member.email}}"></span>
</li>
</ul>
</div>

View file

@ -79,13 +79,13 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
</td>
<td data-title="Name: ">
<a data-ls-attrs="href=/console/users/user?id={{user.$id}}&project={{router.params.project}}">
<span data-ls-bind="{{user.name}}"></span>
<span data-ls-bind="{{user.name}}" data-ls-attrs="title={{user.name}}"></span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} !== ''">Unknown</span>
<span data-ls-if="{{user.name|escape}} === '' && {{user.email}} === ''">Anonymous User</span>
</a>
</td>
<td data-title="Email: ">
<small data-ls-bind="{{user.email}}"></span>
<small data-ls-bind="{{user.email}}" data-ls-attrs="title={{user.email}}"></span>
</td>
<td data-title="Status: ">
<span data-ls-if="{{user.emailVerification}} === true && {{user.status}} === true">
@ -245,7 +245,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<img src="" data-ls-attrs="src={{team.name|avatar}}" data-size="45" alt="Collection Avatar" class="avatar margin-end pull-start" loading="lazy" width="30" height="30" />
</td>
<td data-title="Name: ">
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}"></a>
<a data-ls-attrs="href=/console/users/teams/team?id={{team.$id}}&project={{router.params.project}}" data-ls-bind="{{team.name}}" data-ls-attrs="title={{team.name}}"></a>
</td>
<td data-title="Members: "><span data-ls-bind="{{team.sum}} members"></span></td>
<td data-title="Date Created: "><small data-ls-bind="{{team.dateCreated|dateText}}"></small></td>
@ -406,7 +406,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<img src="<?php echo $this->escape($icon); ?>?buster=<?php echo APP_CACHE_BUSTER; ?>" alt="Email/Password Logo" class="pull-start provider margin-end" />
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <spann class="text-fade text-size-xs">soon</span><?php endif; ?>
<span class="text-size-small"><?php echo $this->escape($name); ?><?php if(!$enabled): ?> <span class="text-fade text-size-xs">soon</span><?php endif; ?>
<?php if( in_array($key, ['usersAuthMagicURL', 'usersAuthInvites']) && !$smtpEnabled): ?>
<p class="margin-bottom-no text-one-liner text-size-small text-danger">

View file

@ -295,8 +295,8 @@
data-param-user-id="{{router.params.id}}"
data-event="load,users.update">
<div data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
No sessions available.
<div class="box margin-top margin-bottom" data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No sessions available.</h3>
</div>
<div data-ls-if="{{sessions.sessions.length}} !== 0" style="display: none">
@ -372,8 +372,8 @@
data-param-user-id="{{router.params.id}}"
data-event="load,logs-load">
<div data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
No logs available.
<div class="box margin-top margin-bottom" data-ls-if="{{logs.logs.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<h3 class="text-bold margin-bottom-no">No logs available.</h3>
</div>
<div class="box" data-ls-if="{{logs.logs.length}} !== 0" style="display: none">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -162,17 +162,6 @@
pre {
overflow: auto;
}
}
button.close {
width: 30px;
height: 30px;
line-height: 30px;
padding: 0;
margin: 0;
background: var(--config-color-normal);
color: var(--config-color-background-fade);
border-radius: 50%;
}
.paging {

View file

@ -239,6 +239,20 @@ button,
}
}
button.close {
width: 30px;
height: 30px;
line-height: 30px;
padding: 0;
margin: 0;
background: var(--config-color-normal);
color: var(--config-color-background-fade);
border-radius: 50%;
.icon-cancel::before{line-height:1;}
&.is-margin-top-10{margin-top:10px;}
}
label {
margin-bottom: 15px;
display: block;
@ -622,6 +636,10 @@ input[type=checkbox], input[type=radio] {
}
}
input[type=checkbox] {
&:after {border-radius:4px;}
}
.input-copy {
position: relative;

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

@ -19,9 +19,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

@ -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'));
}
}