diff --git a/CHANGES.md b/CHANGES.md
index b36f2e5109..7d43e22c94 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,7 @@
- API Key name max length is now 128 chars and not 256 for better API consistency
- Task name max length is now 128 chars and not 256 for better API consistency
- Platform name max length is now 128 chars and not 256 for better API consistency
+- Webhooks payloads are now exactly the same as any of the API response objects
- New and consistent response format for all API object + new response examples in the docs
- Removed user roles attribute from user object (can be fetched from /v1/teams/memberships) **
- Removed type attribute from session object response (used only internally)
diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php
index 9dcd2109ef..5a86298aae 100644
--- a/app/controllers/api/account.php
+++ b/app/controllers/api/account.php
@@ -41,7 +41,7 @@ App::post('/v1/account')
->param('email', '', function () { return new Email(); }, 'User email.')
->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
->param('name', '', function () { return new Text(128); }, 'User name. Max length: 128 chars.', true)
- ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) use ($oauth2Keys) {
+ ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) {
/** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $project */
@@ -107,13 +107,6 @@ App::post('/v1/account')
throw new Exception('Failed saving user to DB', 500);
}
- $webhooks
- ->setParam('payload', [
- 'name' => $name,
- 'email' => $email,
- ])
- ;
-
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.create')
@@ -237,14 +230,7 @@ App::post('/v1/account/sessions')
if (false === $profile) {
throw new Exception('Failed saving user to DB', 500);
}
-
- $webhooks
- ->setParam('payload', [
- 'name' => $profile->getAttribute('name', ''),
- 'email' => $profile->getAttribute('email', ''),
- ])
- ;
-
+
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.sessions.create')
@@ -990,10 +976,7 @@ App::delete('/v1/account')
;
$webhooks
- ->setParam('payload', [
- 'name' => $user->getAttribute('name', ''),
- 'email' => $user->getAttribute('email', ''),
- ])
+ ->setParam('payload', $response->output($user, Response::MODEL_USER))
;
if (!Config::getParam('domainVerification')) {
@@ -1048,10 +1031,7 @@ App::delete('/v1/account/sessions/:sessionId')
;
$webhooks
- ->setParam('payload', [
- 'name' => $user->getAttribute('name', ''),
- 'email' => $user->getAttribute('email', ''),
- ])
+ ->setParam('payload', $response->output($user, Response::MODEL_USER))
;
if (!Config::getParam('domainVerification')) {
@@ -1105,12 +1085,9 @@ App::delete('/v1/account/sessions')
->setParam('event', 'account.sessions.delete')
->setParam('resource', '/user/'.$user->getId())
;
-
+
$webhooks
- ->setParam('payload', [
- 'name' => $user->getAttribute('name', ''),
- 'email' => $user->getAttribute('email', ''),
- ])
+ ->setParam('payload', $response->output($user, Response::MODEL_USER))
;
if (!Config::getParam('domainVerification')) {
diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php
index c0bac82cb4..5413d49788 100644
--- a/app/controllers/api/database.php
+++ b/app/controllers/api/database.php
@@ -2,7 +2,6 @@
use Utopia\App;
use Utopia\Exception;
-use Utopia\Response;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Utopia\Validator\Text;
@@ -20,6 +19,7 @@ use Appwrite\Database\Validator\Collection;
use Appwrite\Database\Validator\Authorization;
use Appwrite\Database\Exception\Authorization as AuthorizationException;
use Appwrite\Database\Exception\Structure as StructureException;
+use Appwrite\Utopia\Response;
App::post('/v1/database/collections')
->desc('Create Collection')
@@ -77,22 +77,14 @@ App::post('/v1/database/collections')
throw new Exception('Failed saving collection to DB', 500);
}
- $data = $data->getArrayCopy();
-
- $webhooks
- ->setParam('payload', $data)
- ;
-
$audits
->setParam('event', 'database.collections.create')
- ->setParam('resource', 'database/collection/'.$data['$id'])
- ->setParam('data', $data)
+ ->setParam('resource', 'database/collection/'.$data->getId())
+ ->setParam('data', $data->getArrayCopy())
;
- $response
- ->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($data)
- ;
+ $response->setStatusCode(Response::STATUS_CODE_CREATED);
+ $response->dynamic($data, Response::MODEL_COLLECTION);
}, ['response', 'projectDB', 'webhooks', 'audits']);
App::get('/v1/database/collections')
@@ -123,7 +115,10 @@ App::get('/v1/database/collections')
],
]);
- $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
+ $response->dynamic(new Document([
+ 'sum' => $projectDB->getSum(),
+ 'collections' => $results
+ ]), Response::MODEL_COLLECTION_LIST);
}, ['response', 'projectDB']);
App::get('/v1/database/collections/:collectionId')
@@ -145,74 +140,9 @@ App::get('/v1/database/collections/:collectionId')
throw new Exception('Collection not found', 404);
}
- $response->json($collection->getArrayCopy());
+ $response->dynamic($collection, Response::MODEL_COLLECTION);
}, ['response', 'projectDB']);
-// App::get('/v1/database/collections/:collectionId/logs')
-// ->desc('Get Collection Logs')
-// ->groups(['api', 'database'])
-// ->label('scope', 'collections.read')
-// ->label('sdk.platform', [APP_PLATFORM_SERVER])
-// ->label('sdk.namespace', 'database')
-// ->label('sdk.method', 'getCollectionLogs')
-// ->label('sdk.description', '/docs/references/database/get-collection-logs.md')
-// ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
-// ->action(
-// function ($collectionId) use ($response, $register, $projectDB, $project) {
-// $collection = $projectDB->getDocument($collectionId, false);
-
-// if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
-// throw new Exception('Collection not found', 404);
-// }
-
-// $adapter = new AuditAdapter($register->get('db'));
-// $adapter->setNamespace('app_'.$project->getId());
-
-// $audit = new Audit($adapter);
-
-// $countries = Locale::getText('countries');
-
-// $logs = $audit->getLogsByResource('database/collection/'.$collection->getId());
-
-// $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
-// $output = [];
-
-// foreach ($logs as $i => &$log) {
-// $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
-
-// $dd = new DeviceDetector($log['userAgent']);
-
-// $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
-
-// $dd->parse();
-
-// $output[$i] = [
-// 'event' => $log['event'],
-// 'ip' => $log['ip'],
-// 'time' => strtotime($log['time']),
-// 'OS' => $dd->getOs(),
-// 'client' => $dd->getClient(),
-// 'device' => $dd->getDevice(),
-// 'brand' => $dd->getBrand(),
-// 'model' => $dd->getModel(),
-// 'geo' => [],
-// ];
-
-// try {
-// $record = $reader->country($log['ip']);
-// $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
-// $output[$i]['geo']['country'] = $record->country->name;
-// $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
-// } catch (\Exception $e) {
-// $output[$i]['geo']['isoCode'] = '--';
-// $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
-// }
-// }
-
-// $response->json($output);
-// }
-// );
-
App::put('/v1/database/collections/:collectionId')
->desc('Update Collection')
->groups(['api', 'database'])
@@ -274,19 +204,13 @@ App::put('/v1/database/collections/:collectionId')
throw new Exception('Failed saving collection to DB', 500);
}
- $data = $collection->getArrayCopy();
-
- $webhooks
- ->setParam('payload', $data)
- ;
-
$audits
->setParam('event', 'database.collections.update')
- ->setParam('resource', 'database/collections/'.$data['$id'])
- ->setParam('data', $data)
+ ->setParam('resource', 'database/collections/'.$collection->getId())
+ ->setParam('data', $collection->getArrayCopy())
;
- $response->json($collection->getArrayCopy());
+ $response->dynamic($collection, Response::MODEL_COLLECTION);
}, ['response', 'projectDB', 'webhooks', 'audits']);
App::delete('/v1/database/collections/:collectionId')
@@ -315,16 +239,14 @@ App::delete('/v1/database/collections/:collectionId')
throw new Exception('Failed to remove collection from DB', 500);
}
- $data = $collection->getArrayCopy();
-
$webhooks
- ->setParam('payload', $data)
+ ->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION))
;
$audits
->setParam('event', 'database.collections.delete')
- ->setParam('resource', 'database/collections/'.$data['$id'])
- ->setParam('data', $data)
+ ->setParam('resource', 'database/collections/'.$collection->getId())
+ ->setParam('data', $collection->getArrayCopy())
;
$response->noContent();
@@ -433,22 +355,17 @@ App::post('/v1/database/collections/:collectionId/documents')
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
}
- $data = $data->getArrayCopy();
-
- $webhooks
- ->setParam('payload', $data)
- ;
-
$audits
->setParam('event', 'database.documents.create')
->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data)
+ ->setParam('data', $data->getArrayCopy())
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
- ->json($data)
;
+
+ $response->dynamic($data, Response::MODEL_ANY);
}, ['response', 'projectDB', 'webhooks', 'audits']);
App::get('/v1/database/collections/:collectionId/documents')
@@ -531,27 +448,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('No document found', 404);
}
- $output = $document->getArrayCopy();
-
- $paths = \explode('/', $request->getParam('q', ''));
- $paths = \array_slice($paths, 7, \count($paths));
-
- if (\count($paths) > 0) {
- if (\count($paths) % 2 == 1) {
- $output = $document->getAttribute(\implode('.', $paths));
- } else {
- $id = (int) \array_pop($paths);
- $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths)));
- }
-
- $output = ($output instanceof Document) ? $output->getArrayCopy() : $output;
-
- if (!\is_array($output)) {
- throw new Exception('No document found', 404);
- }
- }
-
- $response->json($output);
+ $response->dynamic($document, Response::MODEL_ANY);
}, ['request', 'response', 'projectDB']);
App::patch('/v1/database/collections/:collectionId/documents/:documentId')
@@ -620,19 +517,13 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('Failed saving document to DB', 500);
}
- $data = $data->getArrayCopy();
-
- $webhooks
- ->setParam('payload', $data)
- ;
-
$audits
->setParam('event', 'database.documents.update')
- ->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data)
+ ->setParam('resource', 'database/document/'.$data->getId())
+ ->setParam('data', $data->getArrayCopy())
;
- $response->json($data);
+ $response->dynamic($data, Response::MODEL_ANY);
}, ['response', 'projectDB', 'webhooks', 'audits']);
App::delete('/v1/database/collections/:collectionId/documents/:documentId')
@@ -673,16 +564,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
throw new Exception('Failed to remove document from DB', 500);
}
- $data = $document->getArrayCopy();
-
$webhooks
- ->setParam('payload', $data)
+ ->setParam('payload', $response->output($document, Response::MODEL_ANY))
;
-
+
$audits
->setParam('event', 'database.documents.delete')
- ->setParam('resource', 'database/document/'.$data['$id'])
- ->setParam('data', $data) // Audit document in case of malicious or disastrous action
+ ->setParam('resource', 'database/document/'.$document->getId())
+ ->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action
;
$response->noContent();
diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php
index 1fd15c615c..710264b852 100644
--- a/app/controllers/api/storage.php
+++ b/app/controllers/api/storage.php
@@ -141,10 +141,6 @@ App::post('/v1/storage/files')
throw new Exception('Failed saving file to DB', 500);
}
- $webhooks
- ->setParam('payload', $file->getArrayCopy())
- ;
-
$audits
->setParam('event', 'storage.files.create')
->setParam('resource', 'storage/files/'.$file->getId())
@@ -504,10 +500,6 @@ App::put('/v1/storage/files/:fileId')
throw new Exception('Failed saving file to DB', 500);
}
- $webhooks
- ->setParam('payload', $file->getArrayCopy())
- ;
-
$audits
->setParam('event', 'storage.files.update')
->setParam('resource', 'storage/files/'.$file->getId())
@@ -546,11 +538,7 @@ App::delete('/v1/storage/files/:fileId')
throw new Exception('Failed to remove file from DB', 500);
}
}
-
- $webhooks
- ->setParam('payload', $file->getArrayCopy())
- ;
-
+
$audits
->setParam('event', 'storage.files.delete')
->setParam('resource', 'storage/files/'.$file->getId())
diff --git a/app/controllers/general.php b/app/controllers/general.php
index 2c3df992f9..9db6c0f2f1 100644
--- a/app/controllers/general.php
+++ b/app/controllers/general.php
@@ -274,11 +274,18 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi
/** @var bool $mode */
if (!empty($functions->getParam('event'))) {
- $functions->setParam('payload', $webhooks->getParam('payload'));
+ if(empty($functions->getParam('payload'))) {
+ $functions->setParam('payload', $response->getPayload());
+ }
+
$functions->trigger();
}
if (!empty($webhooks->getParam('event'))) {
+ if(empty($webhooks->getParam('payload'))) {
+ $webhooks->setParam('payload', $response->getPayload());
+ }
+
$webhooks->trigger();
}
diff --git a/app/views/console/webhooks/index.phtml b/app/views/console/webhooks/index.phtml
index 99411f5767..47c7d30f01 100644
--- a/app/views/console/webhooks/index.phtml
+++ b/app/views/console/webhooks/index.phtml
@@ -136,7 +136,7 @@ $events = array_keys($this->getParam('events', []));
- ( events)
+ ( events)
(SSL/TLS Disabled)
diff --git a/docker-compose.yml b/docker-compose.yml
index c5a090479a..dd44ea52c4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -134,6 +134,7 @@ services:
depends_on:
- redis
- mariadb
+ - request-catcher
environment:
- _APP_ENV
- _APP_REDIS_HOST
@@ -292,7 +293,7 @@ services:
- MYSQL_PASSWORD=password
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
- maildev:
+ maildev: # used mainly for dev tests
image: djfarrelly/maildev
container_name: appwrite-maildev
restart: unless-stopped
@@ -301,6 +302,15 @@ services:
networks:
- appwrite
+ request-catcher: # used mainly for dev tests
+ image: smarterdm/http-request-catcher
+ container_name: appwrite-request-catcher
+ restart: unless-stopped
+ ports:
+ - '5000:5000'
+ networks:
+ - appwrite
+
# smtp:
# image: appwrite/smtp:1.0.1
# container_name: appwrite-smtp
diff --git a/src/Appwrite/Database/Document.php b/src/Appwrite/Database/Document.php
index 2c0ad4131d..575b86bff7 100644
--- a/src/Appwrite/Database/Document.php
+++ b/src/Appwrite/Database/Document.php
@@ -21,7 +21,7 @@ class Document extends ArrayObject
* @param int $flags
* @param string $iterator_class
*/
- public function __construct($input = null, $flags = 0, $iterator_class = 'ArrayIterator')
+ public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
{
foreach ($input as $key => &$value) {
if (\is_array($value)) {
diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php
index 3795778261..70b57c361f 100644
--- a/src/Appwrite/Utopia/Response.php
+++ b/src/Appwrite/Utopia/Response.php
@@ -7,6 +7,7 @@ use Utopia\Swoole\Response as SwooleResponse;
use Swoole\Http\Response as SwooleHTTPResponse;
use Appwrite\Database\Document;
use Appwrite\Utopia\Response\Model;
+use Appwrite\Utopia\Response\Model\Any;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Collection;
use Appwrite\Utopia\Response\Model\Continent;
@@ -36,6 +37,7 @@ use Appwrite\Utopia\Response\Model\Webhook;
class Response extends SwooleResponse
{
// General
+ const MODEL_ANY = 'any';
const MODEL_LOG = 'log';
const MODEL_LOG_LIST = 'logList';
const MODEL_ERROR = 'error';
@@ -101,6 +103,11 @@ class Response extends SwooleResponse
const MODEL_DOMAIN = 'domain';
const MODEL_DOMAIN_LIST = 'domainList';
+ /**
+ * @var array
+ */
+ protected $payload = [];
+
/**
* Response constructor.
*/
@@ -111,7 +118,7 @@ class Response extends SwooleResponse
->setModel(new Error())
->setModel(new ErrorDev())
// Lists
- ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'users', self::MODEL_COLLECTION))
+ ->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG, false))
@@ -133,6 +140,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
// Entities
+ ->setModel(new Any())
->setModel(new Collection())
->setModel(new Rule())
->setModel(new Log())
@@ -210,12 +218,16 @@ class Response extends SwooleResponse
/**
* Generate valid response object from document data
*/
- protected function output(Document $document, string $model): array
+ public function output(Document $document, string $model): array
{
$data = $document;
$model = $this->getModel($model);
$output = [];
+ if($model->isAny()) {
+ return $document->getArrayCopy();
+ }
+
foreach($model->getRules() as $key => $rule) {
if(!$document->isSet($key)) {
if(!is_null($rule['default'])) {
@@ -245,6 +257,8 @@ class Response extends SwooleResponse
$output[$key] = $data[$key];
}
+ $this->payload = $output;
+
return $output;
}
@@ -269,4 +283,12 @@ class Response extends SwooleResponse
->send(yaml_emit($data, YAML_UTF8_ENCODING))
;
}
+
+ /**
+ * @return array
+ */
+ public function getPayload():array
+ {
+ return $this->payload;
+ }
}
diff --git a/src/Appwrite/Utopia/Response/Model.php b/src/Appwrite/Utopia/Response/Model.php
index b50224935b..0a113aa7b2 100644
--- a/src/Appwrite/Utopia/Response/Model.php
+++ b/src/Appwrite/Utopia/Response/Model.php
@@ -4,6 +4,14 @@ namespace Appwrite\Utopia\Response;
abstract class Model
{
+ /**
+ * @var bool
+ */
+ protected $any = false;
+
+ /**
+ * @var array
+ */
protected $rules = [];
/**
@@ -45,4 +53,9 @@ abstract class Model
return $this;
}
+
+ public function isAny(): bool
+ {
+ return $this->any;
+ }
}
\ No newline at end of file
diff --git a/src/Appwrite/Utopia/Response/Model/Any.php b/src/Appwrite/Utopia/Response/Model/Any.php
new file mode 100644
index 0000000000..1a5defec66
--- /dev/null
+++ b/src/Appwrite/Utopia/Response/Model/Any.php
@@ -0,0 +1,34 @@
+ 'Rule ID.',
'example' => '5e5ea5c16897e',
])
+ ->addRule('$collection', [ // TODO remove this from public response
+ 'type' => 'string',
+ 'description' => 'Rule Collection.',
+ 'example' => '5e5e66c16897e',
+ ])
->addRule('type', [
'type' => 'string',
'description' => 'Rule type. Possible values: ',
@@ -34,6 +39,7 @@ class Rule extends Model
'type' => 'string',
'description' => 'Rule default value.',
'example' => 'Movie Name',
+ 'default' => '',
])
->addRule('array', [
'type' => 'boolean',
@@ -45,6 +51,13 @@ class Rule extends Model
'description' => 'Is required?',
'example' => true,
])
+ ->addRule('list', [
+ 'type' => 'string',
+ 'description' => 'List of allowed values',
+ 'array' => true,
+ 'default' => [],
+ 'example' => ['5e5ea5c168099'],
+ ])
;
}
diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php
index 61920877b7..07e9ec67e5 100644
--- a/tests/e2e/Scopes/Scope.php
+++ b/tests/e2e/Scopes/Scope.php
@@ -34,6 +34,7 @@ abstract class Scope extends TestCase
protected function getLastEmail():array
{
sleep(10);
+
$emails = json_decode(file_get_contents('http://maildev/email'), true);
if($emails && is_array($emails)) {
@@ -43,6 +44,16 @@ abstract class Scope extends TestCase
return [];
}
+ protected function getLastRequest():array
+ {
+ sleep(10);
+
+ $resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
+ $resquest['data'] = json_decode($resquest['data'], true);
+
+ return $resquest;
+ }
+
/**
* @return array
*/
diff --git a/tests/e2e/Services/Workers/WebhooksTest.php b/tests/e2e/Services/Workers/WebhooksTest.php
new file mode 100644
index 0000000000..67c51785cd
--- /dev/null
+++ b/tests/e2e/Services/Workers/WebhooksTest.php
@@ -0,0 +1,152 @@
+client->call(Client::METHOD_POST, '/teams', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'name' => 'Project Test',
+ ]);
+
+ $this->assertEquals(201, $team['headers']['status-code']);
+ $this->assertEquals('Project Test', $team['body']['name']);
+ $this->assertNotEmpty($team['body']['$id']);
+
+ $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'name' => 'Project Test',
+ 'teamId' => $team['body']['$id'],
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertEquals('Project Test', $response['body']['name']);
+ $this->assertEquals($team['body']['$id'], $response['body']['teamId']);
+ $this->assertArrayHasKey('platforms', $response['body']);
+ $this->assertArrayHasKey('webhooks', $response['body']);
+ $this->assertArrayHasKey('keys', $response['body']);
+ $this->assertArrayHasKey('tasks', $response['body']);
+
+ $projectId = $response['body']['$id'];
+
+ /**
+ * Test for FAILURE
+ */
+ $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'name' => '',
+ 'teamId' => $team['body']['$id'],
+ ]);
+
+ $this->assertEquals(400, $response['headers']['status-code']);
+
+ $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $this->getProject()['$id'],
+ ], $this->getHeaders()), [
+ 'name' => 'Project Test',
+ ]);
+
+ $this->assertEquals(400, $response['headers']['status-code']);
+
+ return ['projectId' => $projectId];
+ }
+
+ /**
+ * @depends testCreateProject
+ */
+ public function testCreateWebhook($data): array
+ {
+ $id = (isset($data['projectId'])) ? $data['projectId'] : '';
+
+ $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 Worker Test',
+ 'events' => ['account.create', 'account.update.email'],
+ 'url' => 'http://request-catcher:5000/webhook',
+ 'security' => true,
+ 'httpUser' => 'username',
+ 'httpPass' => 'password',
+ ]);
+
+ $this->assertEquals(201, $response['headers']['status-code']);
+ $this->assertNotEmpty($response['body']['$id']);
+ $this->assertContains('account.create', $response['body']['events']);
+ $this->assertContains('account.update.email', $response['body']['events']);
+ $this->assertCount(2, $response['body']['events']);
+ $this->assertEquals('http://request-catcher:5000/webhook', $response['body']['url']);
+ $this->assertIsBool($response['body']['security']);
+ $this->assertEquals(true, $response['body']['security']);
+ $this->assertEquals('username', $response['body']['httpUser']);
+
+ $data = array_merge($data, ['webhookId' => $response['body']['$id']]);
+
+ /**
+ * Test for FAILURE
+ */
+
+ return $data;
+ }
+
+ /**
+ * @depends testCreateWebhook
+ */
+ public function testCreateAccount($data)
+ {
+ $projectId = (isset($data['projectId'])) ? $data['projectId'] : '';
+ $email = uniqid().'webhook.user@localhost.test';
+ $password = 'password';
+ $name = 'User Name';
+
+ /**
+ * Test for SUCCESS
+ */
+ $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
+ 'origin' => 'http://localhost',
+ 'content-type' => 'application/json',
+ 'x-appwrite-project' => $projectId,
+ ]), [
+ 'email' => $email,
+ 'password' => $password,
+ 'name' => $name,
+ ]);
+
+ $this->assertEquals($response['headers']['status-code'], 201);
+
+ $webhook = $this->getLastRequest();
+
+ $this->assertNotEmpty($webhook['data']);
+ $this->assertNotEmpty($webhook['data']['$id']);
+ $this->assertIsNumeric($webhook['data']['status']);
+ $this->assertIsNumeric($webhook['data']['registration']);
+ $this->assertEquals($webhook['data']['email'], $email);
+ $this->assertEquals($webhook['data']['name'], $name);
+ $this->assertIsBool($webhook['data']['emailVerification']);
+ $this->assertIsArray($webhook['data']['prefs']);
+ $this->assertIsArray($webhook['data']['roles']);
+ }
+}
\ No newline at end of file