mirror of
https://github.com/appwrite/appwrite
synced 2026-05-06 06:48:22 +00:00
Merge pull request #974 from appwrite/feat-graphql-support
Feat graphql support
This commit is contained in:
commit
213d17ffad
98 changed files with 10301 additions and 229 deletions
3
.env
3
.env
|
|
@ -81,6 +81,9 @@ _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
|
|||
_APP_USAGE_STATS=enabled
|
||||
_APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_GRAPHQL_MAX_BATCH_SIZE=10
|
||||
_APP_GRAPHQL_MAX_COMPLEXITY=250
|
||||
_APP_GRAPHQL_MAX_DEPTH=3
|
||||
DOCKERHUB_PULL_USERNAME=
|
||||
DOCKERHUB_PULL_PASSWORD=
|
||||
DOCKERHUB_PULL_EMAIL=
|
||||
35
.gitattributes
vendored
35
.gitattributes
vendored
|
|
@ -1,28 +1,7 @@
|
|||
app/config/* linguist-detectable=false
|
||||
app/config/*/* linguist-detectable=false
|
||||
app/config/*/*/* linguist-detectable=false
|
||||
app/config/*/*/*/* linguist-detectable=false
|
||||
app/views/* linguist-detectable=false
|
||||
app/views/*/* linguist-detectable=false
|
||||
app/views/*/*/* linguist-detectable=false
|
||||
app/views/*/*/*/* linguist-detectable=false
|
||||
app/controllers/* linguist-detectable=false
|
||||
app/controllers/*/* linguist-detectable=false
|
||||
app/controllers/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/* linguist-detectable=false
|
||||
app/controllers/*/*/*/*/* linguist-detectable=false
|
||||
src/* linguist-detectable=false
|
||||
src/*/* linguist-detectable=false
|
||||
src/*/*/* linguist-detectable=false
|
||||
src/*/*/*/* linguist-detectable=false
|
||||
src/*/*/*/*/* linguist-detectable=false
|
||||
tests/* linguist-detectable=false
|
||||
tests/*/* linguist-detectable=false
|
||||
tests/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/* linguist-detectable=false
|
||||
tests/*/*/*/*/*/* linguist-detectable=false
|
||||
public/scripts/* linguist-detectable=false
|
||||
public/scripts/*/*/* linguist-detectable=false
|
||||
public/scripts/*/*/*/* linguist-detectable=false
|
||||
public/dist/scripts/* linguist-detectable=false
|
||||
app/config/** linguist-detectable=false
|
||||
app/views/** linguist-detectable=false
|
||||
app/controllers/** linguist-detectable=false
|
||||
src/** linguist-detectable=false
|
||||
tests/** linguist-detectable=false
|
||||
public/scripts/** linguist-detectable=false
|
||||
public/dist/scripts/** linguist-detectable=false
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,6 +5,8 @@
|
|||
/tests/resources/functions/**/code.tar.gz
|
||||
/app/sdks/*
|
||||
/.idea/
|
||||
!/.idea/workspace.xml
|
||||
!/.idea/php.xml
|
||||
.DS_Store
|
||||
.php_cs.cache
|
||||
debug/
|
||||
|
|
|
|||
15
CHANGES.md
15
CHANGES.md
|
|
@ -1,6 +1,15 @@
|
|||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
# Version 1.2.0
|
||||
## Features
|
||||
- Added GraphQL API [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added GraphQL Explorer [#974](https://github.com/appwrite/appwrite/pull/974)
|
||||
- Added ability to set max sessions per user per project [#4831](https://github.com/appwrite/appwrite/pull/4831)
|
||||
|
||||
## Changes
|
||||
- Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780)
|
||||
|
||||
## Bugs
|
||||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
|
||||
# Version 1.1.2
|
||||
## Changes
|
||||
- Released `appwrite/console` [2.0.2](https://github.com/appwrite/console/releases/tag/2.0.2)
|
||||
|
|
@ -27,14 +36,12 @@
|
|||
|
||||
## Bugs
|
||||
- Fix license detection for Flutter and Dart SDKs [#4435](https://github.com/appwrite/appwrite/pull/4435)
|
||||
- Fix missing realtime event for create function deployment [#4574](https://github.com/appwrite/appwrite/pull/4574)
|
||||
- Fix missing `status`, `buildStderr` and `buildStderr` from get deployment response [#4611](https://github.com/appwrite/appwrite/pull/4611)
|
||||
- Fix project pagination in DB usage aggregation [#4517](https://github.com/appwrite/appwrite/pull/4517)
|
||||
- Fix missing file permissions due to cache [#4661](https://github.com/appwrite/appwrite/pull/4661)
|
||||
- Fix usage stats for async function executions [#4674](https://github.com/appwrite/appwrite/pull/4674)
|
||||
|
||||
# Features
|
||||
- Added Auth Duration API to allow users to set the duration of their sessions. [#4618](https://github.com/appwrite/appwrite/pull/4618)
|
||||
|
||||
# Version 1.0.3
|
||||
## Bugs
|
||||
- Fix document audit deletion [#4429](https://github.com/appwrite/appwrite/pull/4429)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Help us keep Appwrite open and inclusive. Please read and follow our [Code of Co
|
|||
|
||||
## Submit a Pull Request 🚀
|
||||
|
||||
Branch naming convention is as follows
|
||||
Branch naming convention is as following
|
||||
|
||||
`TYPE-ISSUE_ID-DESCRIPTION`
|
||||
|
||||
|
|
@ -188,20 +188,23 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
|
|||
├── src # Supporting libraries (each lib has one role, common libs are released as individual projects)
|
||||
│ └── Appwrite
|
||||
│ ├── Auth
|
||||
│ ├── Database
|
||||
│ ├── Detector
|
||||
│ ├── Docker
|
||||
| ├── DSN
|
||||
│ ├── Event
|
||||
│ ├── Extend
|
||||
│ ├── GraphQL
|
||||
│ ├── Messaging
|
||||
│ ├── Migration
|
||||
│ ├── Network
|
||||
│ ├── OpenSSL
|
||||
│ ├── Realtime
|
||||
│ ├── Promises
|
||||
│ ├── Resque
|
||||
│ ├── Specification
|
||||
│ ├── Task
|
||||
│ ├── Template
|
||||
│ ├── URL
|
||||
│ ├── Usage
|
||||
│ └── Utopia
|
||||
└── tests # End to end & unit tests
|
||||
├── e2e
|
||||
|
|
|
|||
|
|
@ -549,4 +549,14 @@ return [
|
|||
'description' => 'Domain verification for the requested domain has failed.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::GRAPHQL_NO_QUERY => [
|
||||
'name' => Exception::GRAPHQL_NO_QUERY,
|
||||
'description' => 'Param "query" is not optional.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::GRAPHQL_TOO_MANY_QUERIES => [
|
||||
'name' => Exception::GRAPHQL_TOO_MANY_QUERIES,
|
||||
'description' => 'Too many queries.',
|
||||
'code' => 400,
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -135,21 +135,44 @@ return [
|
|||
'Java' => 'java',
|
||||
],
|
||||
],
|
||||
// [
|
||||
// 'key' => 'java',
|
||||
// 'name' => 'Java',
|
||||
// 'url' => '',
|
||||
// 'enabled' => false,
|
||||
// 'beta' => false,
|
||||
// 'dev' => false,
|
||||
// 'hidden' => false,
|
||||
// 'family' => APP_PLATFORM_CLIENT,
|
||||
// 'prism' => 'java',
|
||||
// 'source' => false,
|
||||
// 'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
|
||||
// 'gitRepoName' => 'sdk-for-java',
|
||||
// 'gitUserName' => 'appwrite',
|
||||
// ],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_CLIENT,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -172,8 +195,8 @@ return [
|
|||
'family' => APP_PLATFORM_CONSOLE,
|
||||
'prism' => 'console',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
|
||||
'gitUrl' => null,
|
||||
'gitBranch' => null,
|
||||
'gitUrl' => '',
|
||||
'gitBranch' => '',
|
||||
'gitRepoName' => 'sdk-for-console',
|
||||
'gitUserName' => 'appwrite',
|
||||
],
|
||||
|
|
@ -407,6 +430,44 @@ return [
|
|||
'gitUserName' => 'appwrite',
|
||||
'gitBranch' => 'main',
|
||||
],
|
||||
[
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'version' => 'October 2021',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'graphql',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
[
|
||||
'key' => 'rest',
|
||||
'name' => 'REST',
|
||||
'version' => '',
|
||||
'url' => '',
|
||||
'package' => '',
|
||||
'enabled' => true,
|
||||
'beta' => false,
|
||||
'dev' => false,
|
||||
'hidden' => true,
|
||||
'family' => APP_PLATFORM_SERVER,
|
||||
'prism' => 'http',
|
||||
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
|
||||
'gitUrl' => '',
|
||||
'gitRepoName' => '',
|
||||
'gitUserName' => '',
|
||||
'gitBranch' => '',
|
||||
'isSDK' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ $member = [
|
|||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'account',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
|
|
@ -22,6 +23,7 @@ $member = [
|
|||
];
|
||||
|
||||
$admins = [
|
||||
'graphql',
|
||||
'teams.read',
|
||||
'teams.write',
|
||||
'documents.read',
|
||||
|
|
@ -58,6 +60,7 @@ return [
|
|||
'public',
|
||||
'home',
|
||||
'console',
|
||||
'graphql',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'files.read',
|
||||
|
|
@ -85,6 +88,6 @@ return [
|
|||
],
|
||||
Auth::USER_ROLE_APPS => [
|
||||
'label' => 'Applications',
|
||||
'scopes' => ['health.read'],
|
||||
'scopes' => ['health.read', 'graphql'],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -176,14 +176,14 @@ return [
|
|||
'graphql' => [
|
||||
'key' => 'graphql',
|
||||
'name' => 'GraphQL',
|
||||
'subtitle' => 'Appwrite\'s GraphQL Endpoint',
|
||||
'description' => 'GraphQL Endpoint',
|
||||
'subtitle' => 'The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.',
|
||||
'description' => '/docs/services/graphql.md',
|
||||
'controller' => 'api/graphql.php',
|
||||
'sdk' => false,
|
||||
'docs' => false,
|
||||
'docsUrl' => '',
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/graphql',
|
||||
'tests' => true,
|
||||
'optional' => false,
|
||||
'icon' => '',
|
||||
'optional' => true,
|
||||
'icon' => '/images/services/graphql.png',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
1
app/config/specs/open-api3-1.2.x-client.json
Normal file
1
app/config/specs/open-api3-1.2.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.2.x-console.json
Normal file
1
app/config/specs/open-api3-1.2.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/open-api3-1.2.x-server.json
Normal file
1
app/config/specs/open-api3-1.2.x-server.json
Normal file
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
1
app/config/specs/swagger2-1.2.x-client.json
Normal file
1
app/config/specs/swagger2-1.2.x-client.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.2.x-console.json
Normal file
1
app/config/specs/swagger2-1.2.x-console.json
Normal file
File diff suppressed because one or more lines are too long
1
app/config/specs/swagger2-1.2.x-server.json
Normal file
1
app/config/specs/swagger2-1.2.x-server.json
Normal file
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
|
|
@ -868,5 +868,38 @@ return [
|
|||
'filter' => ''
|
||||
],
|
||||
],
|
||||
[
|
||||
'category' => 'GraphQL',
|
||||
'description' => '',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
|
||||
'description' => 'Maximum number of batched queries per request. The default value is 10.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '10',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_COMPLEXITY',
|
||||
'description' => 'Maximum complexity of a GraphQL query. One field adds one to query complexity. Lists multiply the complexity by the number of items requested. The default value is 250.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '250',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_GRAPHQL_MAX_DEPTH',
|
||||
'description' => 'Maximum depth of a GraphQL query. One nested field level adds one to query depth. The default value is 3.',
|
||||
'introduction' => '1.2.0',
|
||||
'default' => '3',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ function createAttribute(string $databaseId, string $collectionId, Document $att
|
|||
$filters = $attribute->getAttribute('filters', []); // filters are hidden from the endpoint
|
||||
$default = $attribute->getAttribute('default');
|
||||
|
||||
|
||||
$db = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
|
||||
if ($db->isEmpty()) {
|
||||
|
|
@ -384,7 +383,7 @@ App::get('/v1/databases/:databaseId/logs')
|
|||
|
||||
App::put('/v1/databases/:databaseId')
|
||||
->desc('Update Database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('event', 'databases.[databaseId].update')
|
||||
->label('audits.event', 'database.update')
|
||||
|
|
@ -398,7 +397,7 @@ App::put('/v1/databases/:databaseId')
|
|||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_DATABASE)
|
||||
->param('databaseId', '', new UID(), 'Database ID.')
|
||||
->param('name', null, new Text(128), 'Collection name. Max length: 128 chars.')
|
||||
->param('name', null, new Text(128), 'Database name. Max length: 128 chars.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('events')
|
||||
|
|
@ -427,7 +426,7 @@ App::put('/v1/databases/:databaseId')
|
|||
|
||||
App::delete('/v1/databases/:databaseId')
|
||||
->desc('Delete Database')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'databases.write')
|
||||
->label('event', 'databases.[databaseId].delete')
|
||||
->label('audits.event', 'database.delete')
|
||||
|
|
@ -729,7 +728,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->desc('Update Collection')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].update')
|
||||
->label('audits.event', 'collection.update')
|
||||
|
|
@ -797,7 +796,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->desc('Delete Collection')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].delete')
|
||||
->label('audits.event', 'collection.delete')
|
||||
|
|
@ -854,7 +853,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
|
||||
->desc('Create String Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -904,7 +903,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
|
||||
->desc('Create Email Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -948,7 +947,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
|
||||
->desc('Create Enum Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1008,7 +1007,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
|
||||
->desc('Create IP Address Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1052,7 +1051,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
|
||||
->desc('Create URL Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1096,7 +1095,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
|
||||
->desc('Create Integer Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1169,7 +1168,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
|
||||
->desc('Create Float Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1245,7 +1244,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
|
|||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
|
||||
->desc('Create Boolean Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
->label('scope', 'collections.write')
|
||||
->label('audits.event', 'attribute.create')
|
||||
|
|
@ -1444,7 +1443,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
|||
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
|
||||
->desc('Delete Attribute')
|
||||
->groups(['api', 'database'])
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].delete')
|
||||
->label('audits.event', 'attribute.delete')
|
||||
|
|
@ -1753,15 +1752,14 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes/:key')
|
|||
$indexes = $collection->getAttribute('indexes');
|
||||
|
||||
// Search for index
|
||||
$indexIndex = array_search($key, array_column($indexes, 'key'));
|
||||
$indexIndex = array_search($key, array_map(fn($idx) => $idx['key'], $indexes));
|
||||
|
||||
if ($indexIndex === false) {
|
||||
throw new Exception(Exception::INDEX_NOT_FOUND);
|
||||
}
|
||||
|
||||
$index = new Document([\array_merge($indexes[$indexIndex], [
|
||||
'collectionId' => $database->getInternalId() . '_' . $collectionId,
|
||||
])]);
|
||||
$index = $indexes[$indexIndex];
|
||||
$index->setAttribute('collectionId', $database->getInternalId() . '_' . $collectionId);
|
||||
|
||||
$response->dynamic($index, Response::MODEL_INDEX);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -619,14 +619,20 @@ App::post('/v1/functions/:functionId/deployments')
|
|||
}
|
||||
|
||||
$file = $request->getFiles('code');
|
||||
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
|
||||
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
|
||||
$upload = new Upload();
|
||||
|
||||
// GraphQL multipart spec adds files with index keys
|
||||
if (empty($file)) {
|
||||
$file = $request->getFiles(0);
|
||||
}
|
||||
|
||||
if (empty($file)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
|
||||
}
|
||||
|
||||
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
|
||||
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
|
||||
$upload = new Upload();
|
||||
|
||||
// Make sure we handle a single file and multiple files the same way
|
||||
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
|
||||
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
|
||||
|
|
|
|||
|
|
@ -1,24 +1,297 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* 1. Map all objects, object-params, object-fields
|
||||
* 2. Parse GraphQL request payload (use: https://github.com/webonyx/graphql-php)
|
||||
* 3. Route request to relevant controllers (of REST API?) / resolvers and aggergate data
|
||||
* 4. Handle errors if any
|
||||
* 5. Returen JSON response
|
||||
* 6. Write tests!
|
||||
*/
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
use GraphQL\Validator\Rules\DisableIntrospection;
|
||||
use GraphQL\Validator\Rules\QueryComplexity;
|
||||
use GraphQL\Validator\Rules\QueryDepth;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
App::get('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.hide', true)
|
||||
->label('sdk.description', '/docs/references/graphql/get.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->param('query', '', new Text(0), 'The query to execute.')
|
||||
->param('operationName', '', new Text(256), 'The name of the operation to execute.', true)
|
||||
->param('variables', '', new Text(0), 'The JSON encoded variables to use in the query.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (string $query, string $operationName, string $variables, Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
if (!empty($operationName)) {
|
||||
$query['operationName'] = $operationName;
|
||||
}
|
||||
|
||||
if (!empty($variables)) {
|
||||
$query['variables'] = \json_decode($variables, true);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql/mutation')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'mutation')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
App::post('/v1/graphql')
|
||||
->desc('GraphQL Endpoint')
|
||||
->groups(['api', 'graphql'])
|
||||
->label('scope', 'public')
|
||||
->action(
|
||||
function () {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GraphQL support is coming soon!', 503);
|
||||
->groups(['graphql'])
|
||||
->label('scope', 'graphql')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'graphql')
|
||||
->label('sdk.method', 'query')
|
||||
->label('sdk.methodType', 'graphql')
|
||||
->label('sdk.description', '/docs/references/graphql/post.md')
|
||||
->label('sdk.parameters', [
|
||||
'query' => ['default' => [], 'validator' => new JSON(), 'description' => 'The query or queries to execute.', 'optional' => false],
|
||||
])
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_ANY)
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-time', 60)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('schema')
|
||||
->inject('promiseAdapter')
|
||||
->action(function (Request $request, Response $response, GQLSchema $schema, Adapter $promiseAdapter) {
|
||||
$query = $request->getParams();
|
||||
|
||||
if ($request->getHeader('x-sdk-graphql') == 'true') {
|
||||
$query = $query['query'];
|
||||
}
|
||||
|
||||
$type = $request->getHeader('content-type');
|
||||
|
||||
if (\str_starts_with($type, 'application/graphql')) {
|
||||
$query = parseGraphql($request);
|
||||
}
|
||||
|
||||
if (\str_starts_with($type, 'multipart/form-data')) {
|
||||
$query = parseMultipart($query, $request);
|
||||
}
|
||||
|
||||
$output = execute($schema, $promiseAdapter, $query);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_OK)
|
||||
->json($output);
|
||||
});
|
||||
|
||||
/**
|
||||
* Execute a GraphQL request
|
||||
*
|
||||
* @param GQLSchema $schema
|
||||
* @param Adapter $promiseAdapter
|
||||
* @param array $query
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
function execute(
|
||||
GQLSchema $schema,
|
||||
Adapter $promiseAdapter,
|
||||
array $query
|
||||
): array {
|
||||
$maxBatchSize = App::getEnv('_APP_GRAPHQL_MAX_BATCH_SIZE', 10);
|
||||
$maxComplexity = App::getEnv('_APP_GRAPHQL_MAX_COMPLEXITY', 250);
|
||||
$maxDepth = App::getEnv('_APP_GRAPHQL_MAX_DEPTH', 3);
|
||||
|
||||
if (!empty($query) && !isset($query[0])) {
|
||||
$query = [$query];
|
||||
}
|
||||
if (empty($query)) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
if (\count($query) > $maxBatchSize) {
|
||||
throw new Exception(Exception::GRAPHQL_TOO_MANY_QUERIES);
|
||||
}
|
||||
foreach ($query as $item) {
|
||||
if (empty($item['query'])) {
|
||||
throw new Exception(Exception::GRAPHQL_NO_QUERY);
|
||||
}
|
||||
}
|
||||
|
||||
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
if (App::getMode() === App::MODE_TYPE_PRODUCTION) {
|
||||
$flags = DebugFlag::NONE;
|
||||
}
|
||||
|
||||
$promises = [];
|
||||
foreach ($query as $indexed) {
|
||||
$promises[] = GraphQL::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$indexed['query'],
|
||||
variableValues: $indexed['variables'] ?? null,
|
||||
operationName: $indexed['operationName'] ?? null,
|
||||
validationRules: $validations
|
||||
);
|
||||
}
|
||||
|
||||
$output = [];
|
||||
$wg = new WaitGroup();
|
||||
$wg->add();
|
||||
$promiseAdapter->all($promises)->then(
|
||||
function (array $results) use (&$output, &$wg, $flags) {
|
||||
try {
|
||||
$output = processResult($results, $flags);
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
}
|
||||
);
|
||||
$wg->wait();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "application/graphql" type request
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseGraphql(Request $request): array
|
||||
{
|
||||
return ['query' => $request->getRawPayload()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an "multipart/form-data" type request
|
||||
*
|
||||
* @param array $query
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
function parseMultipart(array $query, Request $request): array
|
||||
{
|
||||
$operations = \json_decode($query['operations'], true);
|
||||
$map = \json_decode($query['map'], true);
|
||||
|
||||
foreach ($map as $fileKey => $locations) {
|
||||
foreach ($locations as $location) {
|
||||
$items = &$operations;
|
||||
foreach (\explode('.', $location) as $key) {
|
||||
if (!isset($items[$key]) || !\is_array($items[$key])) {
|
||||
$items[$key] = [];
|
||||
}
|
||||
$items = &$items[$key];
|
||||
}
|
||||
$items = $request->getFiles($fileKey);
|
||||
}
|
||||
}
|
||||
|
||||
$query['query'] = $operations['query'];
|
||||
$query['variables'] = $operations['variables'];
|
||||
|
||||
unset($query['operations']);
|
||||
unset($query['map']);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an array of results for output.
|
||||
*
|
||||
* @param $result
|
||||
* @param $debugFlags
|
||||
* @return array
|
||||
*/
|
||||
function processResult($result, $debugFlags): array
|
||||
{
|
||||
// Only one query, return the result
|
||||
if (!isset($result[1])) {
|
||||
return $result[0]->toArray($debugFlags);
|
||||
}
|
||||
|
||||
// Batched queries, return an array of results
|
||||
return \array_map(
|
||||
static function ($item) use ($debugFlags) {
|
||||
return $item->toArray($debugFlags);
|
||||
},
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
App::shutdown()
|
||||
->groups(['schema'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
Schema::setDirty($project->getId());
|
||||
});
|
||||
|
|
|
|||
|
|
@ -417,7 +417,14 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Maximum bucket file size is larger than _APP_STORAGE_LIMIT');
|
||||
}
|
||||
|
||||
|
||||
$file = $request->getFiles('file');
|
||||
|
||||
// GraphQL multipart spec adds files with index keys
|
||||
if (empty($file)) {
|
||||
$file = $request->getFiles(0);
|
||||
}
|
||||
|
||||
if (empty($file)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_EMPTY);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
'status' => true,
|
||||
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
|
||||
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions,
|
||||
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
|
||||
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
|
||||
'registration' => DateTime::now(),
|
||||
'reset' => false,
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ App::init()
|
|||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $refDomain)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
@ -384,7 +384,7 @@ App::options()
|
|||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ use Appwrite\Event\Database as EventDatabase;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Utopia\App;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Abuse\Adapters\TimeLimit;
|
||||
use Utopia\Cache\Adapter\Filesystem;
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ $http = new Server("0.0.0.0", App::getEnv('PORT', 80));
|
|||
|
||||
$payloadSize = 6 * (1024 * 1024); // 6MB
|
||||
$workerNumber = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$http
|
||||
->set([
|
||||
'worker_num' => $workerNumber,
|
||||
|
|
|
|||
145
app/init.php
145
app/init.php
|
|
@ -18,10 +18,8 @@ ini_set('display_startup_errors', 1);
|
|||
ini_set('default_socket_timeout', -1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use Appwrite\Extend\PDO;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\DSN\DSN;
|
||||
use Appwrite\Event\Audit;
|
||||
|
|
@ -30,17 +28,35 @@ use Appwrite\Event\Delete;
|
|||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Phone;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\PDO;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Usage\Stats;
|
||||
use Appwrite\Utopia\View;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Messaging\Adapters\SMS\Mock;
|
||||
use Utopia\Messaging\Adapters\SMS\Msg91;
|
||||
use Utopia\Messaging\Adapters\SMS\Telesign;
|
||||
|
|
@ -48,31 +64,16 @@ use Utopia\Messaging\Adapters\SMS\TextMagic;
|
|||
use Utopia\Messaging\Adapters\SMS\Twilio;
|
||||
use Utopia\Messaging\Adapters\SMS\Vonage;
|
||||
use Utopia\Registry\Registry;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Database\Adapter\MariaDB;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Structure;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\DatetimeValidator;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Storage\Device\Backblaze;
|
||||
use Utopia\Storage\Device\DOSpaces;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Local;
|
||||
use Utopia\Storage\Device\S3;
|
||||
use Utopia\Storage\Device\Linode;
|
||||
use Utopia\Storage\Device\Wasabi;
|
||||
use Utopia\Storage\Storage;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
|
@ -94,10 +95,11 @@ const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element
|
|||
const APP_LIMIT_SUBQUERY = 1000;
|
||||
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
|
||||
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
|
||||
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
|
||||
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
|
||||
const APP_CACHE_BUSTER = 501;
|
||||
const APP_VERSION_STABLE = '1.1.2';
|
||||
const APP_VERSION_STABLE = '1.2.0';
|
||||
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
|
||||
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
|
||||
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
|
||||
|
|
@ -631,6 +633,9 @@ $register->set('cache', function () {
|
|||
|
||||
return $redis;
|
||||
});
|
||||
$register->set('promiseAdapter', function () {
|
||||
return new Swoole();
|
||||
});
|
||||
|
||||
/*
|
||||
* Localization
|
||||
|
|
@ -1047,3 +1052,93 @@ App::setResource('servers', function () {
|
|||
|
||||
return $languages;
|
||||
});
|
||||
|
||||
App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
$query = Query::getByType($queries, Query::TYPE_LIMIT)[0] ?? null;
|
||||
$limit = $query ? $query->getValue() : APP_LIMIT_LIST_DEFAULT;
|
||||
|
||||
return $complexity * $limit;
|
||||
};
|
||||
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject) {
|
||||
$attrs = Authorization::skip(fn() => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]));
|
||||
|
||||
return \array_map(function ($attr) {
|
||||
return $attr->getArrayCopy();
|
||||
}, $attrs);
|
||||
};
|
||||
|
||||
$urls = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents";
|
||||
},
|
||||
'read' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
'delete' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return "/v1/databases/$databaseId/collections/$collectionId/documents/{$args['documentId']}";
|
||||
},
|
||||
];
|
||||
|
||||
$params = [
|
||||
'list' => function (string $databaseId, string $collectionId, array $args) {
|
||||
return [ 'queries' => $args['queries']];
|
||||
},
|
||||
'create' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$id = $args['id'] ?? 'unique()';
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'documentId' => $id,
|
||||
'collectionId' => $collectionId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
'update' => function (string $databaseId, string $collectionId, array $args) {
|
||||
$documentId = $args['id'];
|
||||
$permissions = $args['permissions'] ?? null;
|
||||
|
||||
unset($args['id']);
|
||||
unset($args['permissions']);
|
||||
|
||||
// Order must be the same as the route params
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'documentId' => $documentId,
|
||||
'data' => $args,
|
||||
'permissions' => $permissions,
|
||||
];
|
||||
},
|
||||
];
|
||||
|
||||
return Schema::build(
|
||||
$utopia,
|
||||
$complexity,
|
||||
$attributes,
|
||||
$urls,
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\CLI\Console;
|
||||
use Appwrite\Spec\Swagger2;
|
||||
use Appwrite\SDK\SDK;
|
||||
use Appwrite\SDK\Language\Android;
|
||||
use Appwrite\SDK\Language\CLI;
|
||||
use Appwrite\SDK\Language\PHP;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\Language\Node;
|
||||
use Appwrite\SDK\Language\Python;
|
||||
use Appwrite\SDK\Language\Ruby;
|
||||
use Appwrite\SDK\Language\Dart;
|
||||
use Appwrite\SDK\Language\Deno;
|
||||
use Appwrite\SDK\Language\DotNet;
|
||||
use Appwrite\SDK\Language\Flutter;
|
||||
use Appwrite\SDK\Language\Go;
|
||||
use Appwrite\SDK\Language\GraphQL;
|
||||
use Appwrite\SDK\Language\Kotlin;
|
||||
use Appwrite\SDK\Language\Android;
|
||||
use Appwrite\SDK\Language\Node;
|
||||
use Appwrite\SDK\Language\PHP;
|
||||
use Appwrite\SDK\Language\Python;
|
||||
use Appwrite\SDK\Language\REST;
|
||||
use Appwrite\SDK\Language\Ruby;
|
||||
use Appwrite\SDK\Language\Swift;
|
||||
use Appwrite\SDK\Language\SwiftClient;
|
||||
use Appwrite\SDK\Language\Apple;
|
||||
use Appwrite\SDK\Language\Web;
|
||||
use Appwrite\SDK\SDK;
|
||||
use Appwrite\Spec\Swagger2;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
|
||||
$cli
|
||||
->task('sdks')
|
||||
|
|
@ -30,7 +32,7 @@ $cli
|
|||
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
|
||||
$message = ($git) ? Console::confirm('Please enter your commit message:') : '';
|
||||
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', 'latest'])) {
|
||||
if (!in_array($version, ['0.6.x', '0.7.x', '0.8.x', '0.9.x', '0.10.x', '0.11.x', '0.12.x', '0.13.x', '0.14.x', '0.15.x', '1.0.x', '1.1.x', '1.2.x', 'latest'])) {
|
||||
throw new Exception('Unknown version given');
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +150,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
$warning = $warning . "\n\n > This is the Swift SDK for integrating with Appwrite from your Swift server-side code. If you're looking for the Apple SDK you should check [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)";
|
||||
break;
|
||||
case 'apple':
|
||||
$config = new SwiftClient();
|
||||
$config = new Apple();
|
||||
break;
|
||||
case 'dotnet':
|
||||
$cover = '';
|
||||
|
|
@ -161,9 +163,14 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
$config = new Kotlin();
|
||||
$warning = $warning . "\n\n > This is the Kotlin SDK for integrating with Appwrite from your Kotlin server-side code. If you're looking for the Android SDK you should check [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)";
|
||||
break;
|
||||
case 'graphql':
|
||||
$config = new GraphQL();
|
||||
break;
|
||||
case 'rest':
|
||||
$config = new REST();
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Language "' . $language['key'] . '" not supported');
|
||||
break;
|
||||
}
|
||||
|
||||
Console::info("Generating {$language['name']} SDK...");
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@
|
|||
"utopia-php/cli": "0.13.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.28.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/domains": "1.1.*",
|
||||
"utopia-php/framework": "0.25.*",
|
||||
"utopia-php/image": "0.5.*",
|
||||
|
|
@ -58,10 +57,11 @@
|
|||
"utopia-php/logger": "0.3.*",
|
||||
"utopia-php/messaging": "0.1.*",
|
||||
"utopia-php/orchestration": "0.6.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.11.*",
|
||||
"utopia-php/swoole": "0.4.*",
|
||||
"utopia-php/websocket": "0.1.0",
|
||||
"utopia-php/swoole": "0.5.*",
|
||||
"utopia-php/websocket": "0.1.*",
|
||||
"resque/php-resque": "1.3.6",
|
||||
"matomo/device-detector": "6.0.0",
|
||||
"dragonmantank/cron-expression": "3.3.1",
|
||||
|
|
@ -69,7 +69,8 @@
|
|||
"phpmailer/phpmailer": "6.6.0",
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0"
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"webonyx/graphql-php": "14.11.*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
|
@ -78,7 +79,7 @@
|
|||
}
|
||||
],
|
||||
"require-dev": {
|
||||
"appwrite/sdk-generator": "0.28.*",
|
||||
"appwrite/sdk-generator": "dev-feat-graphql as 0.28.1",
|
||||
"ext-fileinfo": "*",
|
||||
"phpunit/phpunit": "9.5.20",
|
||||
"squizlabs/php_codesniffer": "^3.6",
|
||||
|
|
@ -93,4 +94,4 @@
|
|||
"php": "8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
117
composer.lock
generated
117
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": "a2a79fc2e9ebdd4d05afa6632c642686",
|
||||
"content-hash": "7994ecac84ccf66f20e64441c03d186b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -2427,16 +2427,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/swoole",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/swoole.git",
|
||||
"reference": "536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538"
|
||||
"reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538",
|
||||
"reference": "536e1f3e78fc0197e4a8ed81b1bf2636a3bc4538",
|
||||
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/c2a3a4f944a2f22945af3cbcb95b13f0769628b1",
|
||||
"reference": "c2a3a4f944a2f22945af3cbcb95b13f0769628b1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2445,6 +2445,7 @@
|
|||
"utopia-php/framework": "0.*.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"swoole/ide-helper": "4.8.3",
|
||||
"vimeo/psalm": "4.15.0"
|
||||
|
|
@ -2459,12 +2460,6 @@
|
|||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Eldad Fux",
|
||||
"email": "team@appwrite.io"
|
||||
}
|
||||
],
|
||||
"description": "An extension for Utopia Framework to work with PHP Swoole as a PHP FPM alternative",
|
||||
"keywords": [
|
||||
"framework",
|
||||
|
|
@ -2477,9 +2472,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/swoole/issues",
|
||||
"source": "https://github.com/utopia-php/swoole/tree/0.4.0"
|
||||
"source": "https://github.com/utopia-php/swoole/tree/0.5.0"
|
||||
},
|
||||
"time": "2022-10-08T14:32:43+00:00"
|
||||
"time": "2022-10-19T22:19:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/system",
|
||||
|
|
@ -2653,21 +2648,87 @@
|
|||
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
|
||||
},
|
||||
"time": "2022-06-03T18:03:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webonyx/graphql-php",
|
||||
"version": "v14.11.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/webonyx/graphql-php.git",
|
||||
"reference": "04a48693acd785330eefd3b0e4fa67df8dfee7c3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/webonyx/graphql-php/zipball/04a48693acd785330eefd3b0e4fa67df8dfee7c3",
|
||||
"reference": "04a48693acd785330eefd3b0e4fa67df8dfee7c3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/amp": "^2.3",
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"nyholm/psr7": "^1.2",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/extension-installer": "^1.0",
|
||||
"phpstan/phpstan": "0.12.82",
|
||||
"phpstan/phpstan-phpunit": "0.12.18",
|
||||
"phpstan/phpstan-strict-rules": "0.12.9",
|
||||
"phpunit/phpunit": "^7.2 || ^8.5",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/promise": "2.*",
|
||||
"simpod/php-coveralls-mirror": "^3.0",
|
||||
"squizlabs/php_codesniffer": "3.5.4"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "To use standard GraphQL server",
|
||||
"react/promise": "To leverage async resolving on React PHP platform"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GraphQL\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A PHP port of GraphQL reference implementation",
|
||||
"homepage": "https://github.com/webonyx/graphql-php",
|
||||
"keywords": [
|
||||
"api",
|
||||
"graphql"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/webonyx/graphql-php/issues",
|
||||
"source": "https://github.com/webonyx/graphql-php/tree/v14.11.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/webonyx-graphql-php",
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2022-09-21T15:35:03+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "0.28.1",
|
||||
"version": "dev-feat-graphql",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "ed8d3daa66589733b49b11c053d524cdf576ffee"
|
||||
"reference": "4fb2026472cd4d2f456c9f1a338b6d4d91da9f09"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ed8d3daa66589733b49b11c053d524cdf576ffee",
|
||||
"reference": "ed8d3daa66589733b49b11c053d524cdf576ffee",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/4fb2026472cd4d2f456c9f1a338b6d4d91da9f09",
|
||||
"reference": "4fb2026472cd4d2f456c9f1a338b6d4d91da9f09",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2675,12 +2736,13 @@
|
|||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"matthiasmullie/minify": "^1.3.68",
|
||||
"php": ">=7.0.0",
|
||||
"php": ">=8.0",
|
||||
"twig/twig": "^3.4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^6.4",
|
||||
"phpunit/phpunit": "^9.5.21"
|
||||
"phpunit/phpunit": "^9.5.21",
|
||||
"squizlabs/php_codesniffer": "^3.6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -2702,9 +2764,9 @@
|
|||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/0.28.1"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/feat-graphql"
|
||||
},
|
||||
"time": "2022-09-22T09:15:54+00:00"
|
||||
"time": "2022-11-17T07:17:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/instantiator",
|
||||
|
|
@ -5181,9 +5243,18 @@
|
|||
"time": "2022-09-28T08:42:51+00:00"
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"aliases": [
|
||||
{
|
||||
"package": "appwrite/sdk-generator",
|
||||
"version": "dev-feat-graphql",
|
||||
"alias": "0.28.1",
|
||||
"alias_normalized": "0.28.1.0"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"stability-flags": {
|
||||
"appwrite/sdk-generator": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ services:
|
|||
- ./docs:/usr/src/code/docs
|
||||
- ./public:/usr/src/code/public
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/local/dev
|
||||
depends_on:
|
||||
- mariadb
|
||||
- redis
|
||||
|
|
@ -174,6 +173,9 @@ services:
|
|||
- _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_GRAPHQL_MAX_BATCH_SIZE
|
||||
- _APP_GRAPHQL_MAX_COMPLEXITY
|
||||
- _APP_GRAPHQL_MAX_DEPTH
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
|
|
@ -615,7 +617,6 @@ services:
|
|||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- ./dev:/usr/local/dev
|
||||
depends_on:
|
||||
- influxdb
|
||||
- mariadb
|
||||
|
|
@ -819,6 +820,18 @@ services:
|
|||
# ports:
|
||||
# - '3001:80'
|
||||
|
||||
graphql-explorer:
|
||||
container_name: appwrite-graphql-explorer
|
||||
image: appwrite/altair:0.3.0
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
ports:
|
||||
- "9509:3000"
|
||||
environment:
|
||||
- SERVER_URL=http://localhost/v1/graphql
|
||||
|
||||
|
||||
# Dev Tools End ------------------------------------------------------------------------------------------
|
||||
|
||||
networks:
|
||||
|
|
|
|||
1
docs/references/graphql/get.md
Normal file
1
docs/references/graphql/get.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Execute a GraphQL query.
|
||||
1
docs/references/graphql/post.md
Normal file
1
docs/references/graphql/post.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Execute a GraphQL mutation.
|
||||
11
docs/services/graphql.md
Normal file
11
docs/services/graphql.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
The GraphQL services allows you to manipulate your Appwrite instance through a single endpoint using GraphQL queries and mutations, asking for exactly what you need and nothing more. You can perform any action available in the REST API.
|
||||
|
||||
> ## GraphQL API vs REST API
|
||||
>
|
||||
> The major difference comes from the way the data is returned. GraphQL returns the data in a structured format, giving you only the nodes you ask for, while REST returns the data in a flat format.
|
||||
>
|
||||
> GraphQL has a single endpoint for all queries and mutations, while the REST API has multiple endpoints for each type of action.
|
||||
>
|
||||
> Both APIs are fully compatible with each other, and you can use them together in the same project.
|
||||
>
|
||||
> Both GraphQL and REST have pros and cons. For example, while GraphQL requests are very flexible and can be more efficient, REST has better support for caching, error handling, and versioning.
|
||||
|
|
@ -855,10 +855,6 @@
|
|||
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
|
||||
GraphQL API
|
||||
<br/>
|
||||
<font style="font-size: 10px">
|
||||
(Coming Soon)
|
||||
</font>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
|
|
@ -23,6 +23,7 @@
|
|||
<directory>./tests/e2e/Services/Realtime</directory>
|
||||
<directory>./tests/e2e/Services/Avatars</directory>
|
||||
<directory>./tests/e2e/Services/Databases</directory>
|
||||
<directory>./tests/e2e/Services/GraphQL</directory>
|
||||
<directory>./tests/e2e/Services/Health</directory>
|
||||
<directory>./tests/e2e/Services/Locale</directory>
|
||||
<directory>./tests/e2e/Services/Projects</directory>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class Auth
|
|||
];
|
||||
|
||||
public const DEFAULT_ALGO = 'argon2';
|
||||
public const DEFAULT_ALGO_OPTIONS = ['memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3];
|
||||
public const DEFAULT_ALGO_OPTIONS = ['type' => 'argon2', 'memoryCost' => 2048, 'timeCost' => 4, 'threads' => 3];
|
||||
|
||||
/**
|
||||
* User Roles.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ class Exception extends \Exception
|
|||
* - Keys
|
||||
* - Platform
|
||||
* - Domain
|
||||
* - GraphQL
|
||||
*/
|
||||
|
||||
/** General */
|
||||
|
|
@ -177,6 +178,10 @@ class Exception extends \Exception
|
|||
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||
|
||||
/** GraphqQL */
|
||||
public const GRAPHQL_NO_QUERY = 'graphql_no_query';
|
||||
public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries';
|
||||
|
||||
protected $type = '';
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
|
|
|
|||
19
src/Appwrite/GraphQL/Exception.php
Normal file
19
src/Appwrite/GraphQL/Exception.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use GraphQL\Error\ClientAware;
|
||||
|
||||
class Exception extends AppwriteException implements ClientAware
|
||||
{
|
||||
public function isClientSafe(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCategory(): string
|
||||
{
|
||||
return 'appwrite';
|
||||
}
|
||||
}
|
||||
88
src/Appwrite/GraphQL/Promises/Adapter.php
Normal file
88
src/Appwrite/GraphQL/Promises/Adapter.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Promises;
|
||||
|
||||
use Appwrite\Promises\Promise;
|
||||
use GraphQL\Executor\Promise\Promise as GQLPromise;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
|
||||
abstract class Adapter implements PromiseAdapter
|
||||
{
|
||||
/**
|
||||
* Returns true if the given value is a {@see Promise}.
|
||||
*
|
||||
* @param $value
|
||||
* @return bool
|
||||
*/
|
||||
public function isThenable($value): bool
|
||||
{
|
||||
return $value instanceof Promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@see Promise} into a {@see GQLPromise}
|
||||
*
|
||||
* @param mixed $thenable
|
||||
* @return GQLPromise
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function convertThenable(mixed $thenable): GQLPromise
|
||||
{
|
||||
if (!$thenable instanceof Promise) {
|
||||
throw new \Exception('Expected instance of Promise got: ' . \gettype($thenable));
|
||||
}
|
||||
|
||||
return new GQLPromise($thenable, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when the passed in promise resolves.
|
||||
*
|
||||
* @param GQLPromise $promise
|
||||
* @param callable|null $onFulfilled
|
||||
* @param callable|null $onRejected
|
||||
* @return GQLPromise
|
||||
*/
|
||||
public function then(
|
||||
GQLPromise $promise,
|
||||
?callable $onFulfilled = null,
|
||||
?callable $onRejected = null
|
||||
): GQLPromise {
|
||||
/** @var Promise $adoptedPromise */
|
||||
$adoptedPromise = $promise->adoptedPromise;
|
||||
|
||||
return new GQLPromise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new promise with the given resolver function.
|
||||
*
|
||||
* @param callable $resolver
|
||||
* @return GQLPromise
|
||||
*/
|
||||
abstract public function create(callable $resolver): GQLPromise;
|
||||
|
||||
/**
|
||||
* Create a new promise that is fulfilled with the given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return GQLPromise
|
||||
*/
|
||||
abstract public function createFulfilled(mixed $value = null): GQLPromise;
|
||||
|
||||
/**
|
||||
* Create a new promise that is rejected with the given reason.
|
||||
*
|
||||
* @param mixed $reason
|
||||
* @return GQLPromise
|
||||
*/
|
||||
abstract public function createRejected(mixed $reason): GQLPromise;
|
||||
|
||||
/**
|
||||
* Create a new promise that resolves when all passed in promises resolve.
|
||||
*
|
||||
* @param array $promisesOrValues
|
||||
* @return GQLPromise
|
||||
*/
|
||||
abstract public function all(array $promisesOrValues): GQLPromise;
|
||||
}
|
||||
42
src/Appwrite/GraphQL/Promises/Adapter/Swoole.php
Normal file
42
src/Appwrite/GraphQL/Promises/Adapter/Swoole.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Promises\Adapter;
|
||||
|
||||
use Appwrite\GraphQL\Promises\Adapter;
|
||||
use Appwrite\Promises\Swoole as SwoolePromise;
|
||||
use GraphQL\Executor\Promise\Promise as GQLPromise;
|
||||
|
||||
class Swoole extends Adapter
|
||||
{
|
||||
public function create(callable $resolver): GQLPromise
|
||||
{
|
||||
$promise = new SwoolePromise(function ($resolve, $reject) use ($resolver) {
|
||||
$resolver($resolve, $reject);
|
||||
});
|
||||
|
||||
return new GQLPromise($promise, $this);
|
||||
}
|
||||
|
||||
public function createFulfilled($value = null): GQLPromise
|
||||
{
|
||||
$promise = new SwoolePromise(function ($resolve, $reject) use ($value) {
|
||||
$resolve($value);
|
||||
});
|
||||
|
||||
return new GQLPromise($promise, $this);
|
||||
}
|
||||
|
||||
public function createRejected($reason): GQLPromise
|
||||
{
|
||||
$promise = new SwoolePromise(function ($resolve, $reject) use ($reason) {
|
||||
$reject($reason);
|
||||
});
|
||||
|
||||
return new GQLPromise($promise, $this);
|
||||
}
|
||||
|
||||
public function all(array $promisesOrValues): GQLPromise
|
||||
{
|
||||
return new GQLPromise(SwoolePromise::all($promisesOrValues), $this);
|
||||
}
|
||||
}
|
||||
308
src/Appwrite/GraphQL/Resolvers.php
Normal file
308
src/Appwrite/GraphQL/Resolvers.php
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Exception as GQLException;
|
||||
use Appwrite\Promises\Swoole;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Route;
|
||||
|
||||
class Resolvers
|
||||
{
|
||||
/**
|
||||
* Create a resolver for a given API {@see Route}.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param ?Route $route
|
||||
* @return callable
|
||||
*/
|
||||
public static function api(
|
||||
App $utopia,
|
||||
?Route $route,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) {
|
||||
/** @var App $utopia */
|
||||
/** @var Response $response */
|
||||
/** @var Request $request */
|
||||
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$path = $route->getPath();
|
||||
foreach ($args as $key => $value) {
|
||||
if (\str_contains($path, '/:' . $key)) {
|
||||
$path = \str_replace(':' . $key, $value, $path);
|
||||
}
|
||||
}
|
||||
|
||||
$request->setMethod($route->getMethod());
|
||||
$request->setURI($path);
|
||||
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$request->setQueryString($args);
|
||||
break;
|
||||
default:
|
||||
$request->setPayload($args);
|
||||
break;
|
||||
}
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for a document in a specified database and collection with a specific method type.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param string $methodType
|
||||
* @return callable
|
||||
*/
|
||||
public static function document(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
string $methodType,
|
||||
): callable {
|
||||
return [self::class, 'document' . \ucfirst($methodType)](
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for getting a document in a specified database and collection.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @return callable
|
||||
*/
|
||||
public static function documentGet(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for listing documents in a specified database and collection.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public static function documentList(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setQueryString($params($databaseId, $collectionId, $args));
|
||||
|
||||
$beforeResolve = function ($payload) {
|
||||
return $payload['documents'];
|
||||
};
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject, $beforeResolve);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for creating a document in a specified database and collection.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public static function documentCreate(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('POST');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for updating a document in a specified database and collection.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @param callable $params
|
||||
* @return callable
|
||||
*/
|
||||
public static function documentUpdate(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('PATCH');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
$request->setPayload($params($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resolver for deleting a document in a specified database and collection.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param string $databaseId
|
||||
* @param string $collectionId
|
||||
* @param callable $url
|
||||
* @return callable
|
||||
*/
|
||||
public static function documentDelete(
|
||||
App $utopia,
|
||||
string $databaseId,
|
||||
string $collectionId,
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
$request->setMethod('DELETE');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
|
||||
self::resolve($utopia, $request, $response, $resolve, $reject);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param App $utopia
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @param callable $resolve
|
||||
* @param callable $reject
|
||||
* @param callable|null $beforeResolve
|
||||
* @param callable|null $beforeReject
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function resolve(
|
||||
App $utopia,
|
||||
Request $request,
|
||||
Response $response,
|
||||
callable $resolve,
|
||||
callable $reject,
|
||||
?callable $beforeResolve = null,
|
||||
?callable $beforeReject = null,
|
||||
): void {
|
||||
// Drop json content type so post args are used directly
|
||||
if (\str_starts_with($request->getHeader('content-type'), 'application/json')) {
|
||||
$request->removeHeader('content-type');
|
||||
}
|
||||
|
||||
$request = clone $request;
|
||||
$utopia->setResource('request', static fn() => $request);
|
||||
$response->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
|
||||
try {
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
|
||||
$utopia->execute($route, $request);
|
||||
} catch (\Throwable $e) {
|
||||
if ($beforeReject) {
|
||||
$e = $beforeReject($e);
|
||||
}
|
||||
$reject($e);
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = $response->getPayload();
|
||||
|
||||
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400) {
|
||||
if ($beforeReject) {
|
||||
$payload = $beforeReject($payload);
|
||||
}
|
||||
$reject(new GQLException(
|
||||
message: $payload['message'],
|
||||
code: $response->getStatusCode()
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($payload as $key => $value) {
|
||||
if (\str_starts_with($key, '$')) {
|
||||
$escapedKey = \str_replace('$', '_', $key);
|
||||
$payload[$escapedKey] = $value;
|
||||
unset($payload[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($beforeResolve) {
|
||||
$payload = $beforeResolve($payload);
|
||||
}
|
||||
|
||||
$resolve($payload);
|
||||
}
|
||||
}
|
||||
269
src/Appwrite/GraphQL/Schema.php
Normal file
269
src/Appwrite/GraphQL/Schema.php
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\Mapper;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema as GQLSchema;
|
||||
use Utopia\App;
|
||||
use Utopia\Exception;
|
||||
use Utopia\Route;
|
||||
|
||||
class Schema
|
||||
{
|
||||
protected static ?GQLSchema $schema = null;
|
||||
protected static array $dirty = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param callable $complexity Function to calculate complexity
|
||||
* @param callable $attributes Function to get attributes
|
||||
* @param array $urls Array of functions to get urls for specific method types
|
||||
* @param array $params Array of functions to build parameters for specific method types
|
||||
* @return GQLSchema
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function build(
|
||||
App $utopia,
|
||||
callable $complexity,
|
||||
callable $attributes,
|
||||
array $urls,
|
||||
array $params,
|
||||
): GQLSchema {
|
||||
App::setResource('utopia:graphql', static function () use ($utopia) {
|
||||
return $utopia;
|
||||
});
|
||||
|
||||
if (!empty(self::$schema)) {
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
$api = static::api(
|
||||
$utopia,
|
||||
$complexity
|
||||
);
|
||||
//$collections = static::collections(
|
||||
// $utopia,
|
||||
// $complexity,
|
||||
// $attributes,
|
||||
// $urls,
|
||||
// $params,
|
||||
//);
|
||||
|
||||
$queries = \array_merge_recursive(
|
||||
$api['query'],
|
||||
//$collections['query']
|
||||
);
|
||||
$mutations = \array_merge_recursive(
|
||||
$api['mutation'],
|
||||
//$collections['mutation']
|
||||
);
|
||||
|
||||
\ksort($queries);
|
||||
\ksort($mutations);
|
||||
|
||||
return static::$schema = new GQLSchema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'fields' => $queries
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'fields' => $mutations
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function iterates all API routes and builds a GraphQL
|
||||
* schema defining types and resolvers for all response models.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param callable $complexity
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static function api(App $utopia, callable $complexity): array
|
||||
{
|
||||
Mapper::init($utopia
|
||||
->getResource('response')
|
||||
->getModels());
|
||||
|
||||
$queries = [];
|
||||
$mutations = [];
|
||||
|
||||
foreach ($utopia->getRoutes() as $routes) {
|
||||
foreach ($routes as $route) {
|
||||
/** @var Route $route */
|
||||
|
||||
$namespace = $route->getLabel('sdk.namespace', '');
|
||||
$method = $route->getLabel('sdk.method', '');
|
||||
$name = $namespace . \ucfirst($method);
|
||||
|
||||
if (empty($name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Mapper::route($utopia, $route, $complexity) as $field) {
|
||||
switch ($route->getMethod()) {
|
||||
case 'GET':
|
||||
$queries[$name] = $field;
|
||||
break;
|
||||
case 'POST':
|
||||
case 'PUT':
|
||||
case 'PATCH':
|
||||
case 'DELETE':
|
||||
$mutations[$name] = $field;
|
||||
break;
|
||||
default:
|
||||
throw new \Exception("Unsupported method: {$route->getMethod()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $queries,
|
||||
'mutation' => $mutations
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates all of a projects attributes and builds GraphQL
|
||||
* queries and mutations for the collections they make up.
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param callable $complexity
|
||||
* @param callable $attributes
|
||||
* @param array $urls
|
||||
* @param array $params
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected static function collections(
|
||||
App $utopia,
|
||||
callable $complexity,
|
||||
callable $attributes,
|
||||
array $urls,
|
||||
array $params,
|
||||
): array {
|
||||
$collections = [];
|
||||
$queryFields = [];
|
||||
$mutationFields = [];
|
||||
$limit = 1000;
|
||||
$offset = 0;
|
||||
|
||||
while (!empty($attrs = $attributes($limit, $offset))) {
|
||||
foreach ($attrs as $attr) {
|
||||
if ($attr['status'] !== 'available') {
|
||||
continue;
|
||||
}
|
||||
$databaseId = $attr['databaseId'];
|
||||
$collectionId = $attr['collectionId'];
|
||||
$key = $attr['key'];
|
||||
$type = $attr['type'];
|
||||
$array = $attr['array'];
|
||||
$required = $attr['required'];
|
||||
$default = $attr['default'];
|
||||
$escapedKey = str_replace('$', '', $key);
|
||||
$collections[$collectionId][$escapedKey] = [
|
||||
'type' => Mapper::attribute(
|
||||
$type,
|
||||
$array,
|
||||
$required
|
||||
),
|
||||
'defaultValue' => $default,
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($collections as $collectionId => $attributes) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $collectionId,
|
||||
'fields' => \array_merge(
|
||||
["_id" => ['type' => Type::string()]],
|
||||
$attributes
|
||||
),
|
||||
]);
|
||||
$attributes = \array_merge(
|
||||
$attributes,
|
||||
Mapper::args('mutate')
|
||||
);
|
||||
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentGet(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['get'],
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => Mapper::args('list'),
|
||||
'resolve' => Resolvers::documentList(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['list'],
|
||||
$params['list'],
|
||||
),
|
||||
'complexity' => $complexity,
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
'resolve' => Resolvers::documentCreate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['create'],
|
||||
$params['create'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
'args' => \array_merge(
|
||||
Mapper::args('id'),
|
||||
\array_map(
|
||||
fn($attr) => $attr['type'] = Type::getNullableType($attr['type']),
|
||||
$attributes
|
||||
)
|
||||
),
|
||||
'resolve' => Resolvers::documentUpdate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['update'],
|
||||
$params['update'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => Mapper::model('none'),
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentDelete(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['delete'],
|
||||
)
|
||||
];
|
||||
}
|
||||
$offset += $limit;
|
||||
}
|
||||
|
||||
return [
|
||||
'query' => $queryFields,
|
||||
'mutation' => $mutationFields
|
||||
];
|
||||
}
|
||||
|
||||
public static function setDirty(string $projectId): void
|
||||
{
|
||||
self::$dirty[$projectId] = true;
|
||||
}
|
||||
}
|
||||
57
src/Appwrite/GraphQL/Types.php
Normal file
57
src/Appwrite/GraphQL/Types.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\Assoc;
|
||||
use Appwrite\GraphQL\Types\InputFile;
|
||||
use Appwrite\GraphQL\Types\Json;
|
||||
use Appwrite\GraphQL\Types\Registry;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class Types
|
||||
{
|
||||
/**
|
||||
* Get the JSON type.
|
||||
*
|
||||
* @return Json
|
||||
*/
|
||||
public static function json(): Type
|
||||
{
|
||||
if (Registry::has(Json::class)) {
|
||||
return Registry::get(Json::class);
|
||||
}
|
||||
$type = new Json();
|
||||
Registry::set(Json::class, $type);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON type.
|
||||
*
|
||||
* @return Json
|
||||
*/
|
||||
public static function assoc(): Type
|
||||
{
|
||||
if (Registry::has(Assoc::class)) {
|
||||
return Registry::get(Assoc::class);
|
||||
}
|
||||
$type = new Assoc();
|
||||
Registry::set(Assoc::class, $type);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the InputFile type.
|
||||
*
|
||||
* @return InputFile
|
||||
*/
|
||||
public static function inputFile(): Type
|
||||
{
|
||||
if (Registry::has(InputFile::class)) {
|
||||
return Registry::get(InputFile::class);
|
||||
}
|
||||
$type = new InputFile();
|
||||
Registry::set(InputFile::class, $type);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
42
src/Appwrite/GraphQL/Types/Assoc.php
Normal file
42
src/Appwrite/GraphQL/Types/Assoc.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Language\AST\BooleanValueNode;
|
||||
use GraphQL\Language\AST\FloatValueNode;
|
||||
use GraphQL\Language\AST\IntValueNode;
|
||||
use GraphQL\Language\AST\ListValueNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\ObjectValueNode;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
|
||||
class Assoc extends Json
|
||||
{
|
||||
public $name = 'Assoc';
|
||||
public $description = 'The `Assoc` scalar type represents associative array values.';
|
||||
|
||||
public function serialize($value)
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return \json_encode($value);
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
if (\is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return \json_decode($value, true);
|
||||
}
|
||||
|
||||
public function parseLiteral(Node $valueNode, ?array $variables = null)
|
||||
{
|
||||
return \json_decode($valueNode->value, true);
|
||||
}
|
||||
}
|
||||
29
src/Appwrite/GraphQL/Types/InputFile.php
Normal file
29
src/Appwrite/GraphQL/Types/InputFile.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
class InputFile extends ScalarType
|
||||
{
|
||||
public $name = 'InputFile';
|
||||
public $description = 'The `InputFile` special type represents a file to be uploaded in the same HTTP request as specified by
|
||||
[graphql-multipart-request-spec](https://github.com/jaydenseric/graphql-multipart-request-spec).';
|
||||
|
||||
public function serialize($value)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function parseLiteral(Node $valueNode, ?array $variables = null)
|
||||
{
|
||||
throw new Error('`InputFile` cannot be hardcoded in query, be sure to conform to GraphQL multipart request specification. Instead got: ' . $valueNode->kind, $valueNode);
|
||||
}
|
||||
}
|
||||
53
src/Appwrite/GraphQL/Types/Json.php
Normal file
53
src/Appwrite/GraphQL/Types/Json.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Language\AST\BooleanValueNode;
|
||||
use GraphQL\Language\AST\FloatValueNode;
|
||||
use GraphQL\Language\AST\IntValueNode;
|
||||
use GraphQL\Language\AST\ListValueNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\ObjectValueNode;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
// https://github.com/webonyx/graphql-php/issues/129#issuecomment-309366803
|
||||
class Json extends ScalarType
|
||||
{
|
||||
public $name = 'Json';
|
||||
public $description = 'The `JSON` scalar type represents JSON values as specified by
|
||||
[ECMA-404](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).';
|
||||
|
||||
public function serialize($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function parseValue($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function parseLiteral(Node $valueNode, ?array $variables = null)
|
||||
{
|
||||
switch ($valueNode) {
|
||||
case $valueNode instanceof StringValueNode:
|
||||
case $valueNode instanceof BooleanValueNode:
|
||||
return $valueNode->value;
|
||||
case $valueNode instanceof IntValueNode:
|
||||
case $valueNode instanceof FloatValueNode:
|
||||
return floatval($valueNode->value);
|
||||
case $valueNode instanceof ObjectValueNode:
|
||||
$value = [];
|
||||
foreach ($valueNode->fields as $field) {
|
||||
$value[$field->name->value] =
|
||||
$this->parseLiteral($field->value);
|
||||
}
|
||||
return $value;
|
||||
case ($valueNode instanceof ListValueNode):
|
||||
return array_map([$this, 'parseLiteral'], $valueNode->values);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
433
src/Appwrite/GraphQL/Types/Mapper.php
Normal file
433
src/Appwrite/GraphQL/Types/Mapper.php
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use Appwrite\GraphQL\Resolvers;
|
||||
use Appwrite\GraphQL\Types;
|
||||
use Exception;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Definition\UnionType;
|
||||
use Utopia\App;
|
||||
use Utopia\Route;
|
||||
use Utopia\Validator;
|
||||
|
||||
class Mapper
|
||||
{
|
||||
private static array $models = [];
|
||||
private static array $args = [];
|
||||
private static array $blacklist = [
|
||||
'/v1/mock',
|
||||
'/v1/graphql',
|
||||
'/v1/account/sessions/oauth2',
|
||||
];
|
||||
|
||||
public static function init(array $models): void
|
||||
{
|
||||
self::$models = $models;
|
||||
|
||||
self::$args = [
|
||||
'id' => [
|
||||
'id' => [
|
||||
'type' => Type::nonNull(Type::string()),
|
||||
],
|
||||
],
|
||||
'list' => [
|
||||
'queries' => [
|
||||
'type' => Type::listOf(Type::nonNull(Type::string())),
|
||||
'defaultValue' => [],
|
||||
],
|
||||
],
|
||||
'mutate' => [
|
||||
'permissions' => [
|
||||
'type' => Type::listOf(Type::nonNull(Type::string())),
|
||||
'defaultValue' => [],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$defaults = [
|
||||
'boolean' => Type::boolean(),
|
||||
'string' => Type::string(),
|
||||
'integer' => Type::int(),
|
||||
'double' => Type::float(),
|
||||
'datetime' => Type::string(),
|
||||
'json' => Types::json(),
|
||||
'none' => Types::json(),
|
||||
'any' => Types::json(),
|
||||
];
|
||||
|
||||
foreach ($defaults as $type => $default) {
|
||||
Registry::set($type, $default);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the registered default arguments for a given key.
|
||||
*
|
||||
* @param string $key
|
||||
* @return array
|
||||
*/
|
||||
public static function args(string $key): array
|
||||
{
|
||||
return self::$args[$key] ?? [];
|
||||
}
|
||||
|
||||
public static function route(
|
||||
App $utopia,
|
||||
Route $route,
|
||||
callable $complexity
|
||||
): iterable {
|
||||
foreach (self::$blacklist as $blacklist) {
|
||||
if (\str_starts_with($route->getPath(), $blacklist)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$names = $route->getLabel('sdk.response.model', 'none');
|
||||
$models = \is_array($names)
|
||||
? \array_map(static fn($m) => static::$models[$m], $names)
|
||||
: [static::$models[$names]];
|
||||
|
||||
foreach ($models as $model) {
|
||||
$type = Mapper::model(\ucfirst($model->getType()));
|
||||
$description = $route->getDesc();
|
||||
$params = [];
|
||||
$list = false;
|
||||
|
||||
foreach ($route->getParams() as $name => $parameter) {
|
||||
if ($name === 'queries') {
|
||||
$list = true;
|
||||
}
|
||||
$parameterType = Mapper::param(
|
||||
$utopia,
|
||||
$parameter['validator'],
|
||||
!$parameter['optional'],
|
||||
$parameter['injections']
|
||||
);
|
||||
$params[$name] = [
|
||||
'type' => $parameterType,
|
||||
'description' => $parameter['description'],
|
||||
];
|
||||
if ($parameter['optional']) {
|
||||
$params[$name]['defaultValue'] = $parameter['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$field = [
|
||||
'type' => $type,
|
||||
'description' => $description,
|
||||
'args' => $params,
|
||||
'resolve' => Resolvers::api($utopia, $route)
|
||||
];
|
||||
|
||||
if ($list) {
|
||||
$field['complexity'] = $complexity;
|
||||
}
|
||||
|
||||
yield $field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type from the registry, creating it if it does not already exist.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Type
|
||||
*/
|
||||
public static function model(string $name): Type
|
||||
{
|
||||
if (Registry::has($name)) {
|
||||
return Registry::get($name);
|
||||
}
|
||||
|
||||
$fields = [];
|
||||
$model = self::$models[\lcfirst($name)];
|
||||
|
||||
// If model has additional properties, explicitly add a 'data' field
|
||||
if ($model->isAny()) {
|
||||
$fields['data'] = [
|
||||
'type' => Type::string(),
|
||||
'description' => 'Additional data',
|
||||
'resolve' => static function ($object, $args, $context, $info) {
|
||||
$data = \array_filter(
|
||||
(array)$object,
|
||||
fn($key) => !\str_starts_with($key, '_'),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
return \json_encode($data, JSON_FORCE_OBJECT);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// If model has no properties, explicitly add a 'status' field
|
||||
// because GraphQL requires at least 1 field per type.
|
||||
if (!$model->isAny() && empty($model->getRules())) {
|
||||
$fields['status'] = [
|
||||
'type' => Type::string(),
|
||||
'description' => 'Status',
|
||||
'resolve' => static fn($object, $args, $context, $info) => 'OK',
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($model->getRules() as $key => $rule) {
|
||||
$escapedKey = str_replace('$', '_', $key);
|
||||
|
||||
if (\is_array($rule['type'])) {
|
||||
$type = self::getUnionType($escapedKey, $rule);
|
||||
} else {
|
||||
$type = self::getObjectType($rule);
|
||||
}
|
||||
|
||||
if ($rule['array']) {
|
||||
$type = Type::listOf($type);
|
||||
}
|
||||
|
||||
$fields[$escapedKey] = [
|
||||
'type' => $type,
|
||||
'description' => $rule['description'],
|
||||
];
|
||||
|
||||
if (!$rule['required']) {
|
||||
$fields[$escapedKey]['defaultValue'] = $rule['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$type = new ObjectType([
|
||||
'name' => $name,
|
||||
'fields' => $fields,
|
||||
]);
|
||||
|
||||
Registry::set($name, $type);
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a {@see Route} parameter to a GraphQL Type
|
||||
*
|
||||
* @param App $utopia
|
||||
* @param Validator|callable $validator
|
||||
* @param bool $required
|
||||
* @param array $injections
|
||||
* @return Type
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function param(
|
||||
App $utopia,
|
||||
Validator|callable $validator,
|
||||
bool $required,
|
||||
array $injections
|
||||
): Type {
|
||||
$validator = \is_callable($validator)
|
||||
? \call_user_func_array($validator, $utopia->getResources($injections))
|
||||
: $validator;
|
||||
|
||||
switch ((!empty($validator)) ? $validator::class : '') {
|
||||
case 'Appwrite\Network\Validator\CNAME':
|
||||
case 'Appwrite\Task\Validator\Cron':
|
||||
case 'Appwrite\Utopia\Database\Validator\CustomId':
|
||||
case 'Appwrite\Network\Validator\Domain':
|
||||
case 'Appwrite\Network\Validator\Email':
|
||||
case 'Appwrite\Event\Validator\Event':
|
||||
case 'Utopia\Validator\HexColor':
|
||||
case 'Appwrite\Network\Validator\Host':
|
||||
case 'Appwrite\Network\Validator\IP':
|
||||
case 'Utopia\Database\Validator\Key':
|
||||
case 'Appwrite\Network\Validator\Origin':
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Utopia\Validator\Text':
|
||||
case 'Utopia\Database\Validator\UID':
|
||||
case 'Appwrite\Network\Validator\URL':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
default:
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Database\Validator\Authorization':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Base':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Buckets':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Documents':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Functions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Memberships':
|
||||
case 'Utopia\Database\Validator\Permissions':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Projects':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries':
|
||||
case 'Utopia\Database\Validator\Roles':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Teams':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Users':
|
||||
case 'Appwrite\Utopia\Database\Validator\Queries\Variables':
|
||||
$type = Type::listOf(Type::string());
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
$type = Type::boolean();
|
||||
break;
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
$type = Type::listOf(self::param(
|
||||
$utopia,
|
||||
$validator->getValidator(),
|
||||
$required,
|
||||
$injections
|
||||
));
|
||||
break;
|
||||
case 'Utopia\Validator\Integer':
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case 'Utopia\Validator\Range':
|
||||
$type = Type::int();
|
||||
break;
|
||||
case 'Utopia\Validator\FloatValidator':
|
||||
$type = Type::float();
|
||||
break;
|
||||
case 'Utopia\Validator\Assoc':
|
||||
$type = Types::assoc();
|
||||
break;
|
||||
case 'Utopia\Validator\JSON':
|
||||
$type = Types::json();
|
||||
break;
|
||||
case 'Utopia\Storage\Validator\File':
|
||||
$type = Types::inputFile();
|
||||
break;
|
||||
}
|
||||
|
||||
if ($required) {
|
||||
$type = Type::nonNull($type);
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an {@see Attribute} to a GraphQL Type
|
||||
*
|
||||
* @param string $type
|
||||
* @param bool $array
|
||||
* @param bool $required
|
||||
* @return Type
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function attribute(string $type, bool $array, bool $required): Type
|
||||
{
|
||||
if ($array) {
|
||||
return Type::listOf(self::attribute(
|
||||
$type,
|
||||
false,
|
||||
$required
|
||||
));
|
||||
}
|
||||
|
||||
$type = match ($type) {
|
||||
'boolean' => Type::boolean(),
|
||||
'integer' => Type::int(),
|
||||
'double' => Type::float(),
|
||||
default => Type::string(),
|
||||
};
|
||||
|
||||
if ($required) {
|
||||
$type = Type::nonNull($type);
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
private static function getObjectType(array $rule): Type
|
||||
{
|
||||
$type = $rule['type'];
|
||||
|
||||
if (Registry::has($type)) {
|
||||
return Registry::get($type);
|
||||
}
|
||||
|
||||
$complexModel = self::$models[$type];
|
||||
return self::model(\ucfirst($complexModel->getType()));
|
||||
}
|
||||
|
||||
private static function getUnionType(string $name, array $rule): Type
|
||||
{
|
||||
$unionName = \ucfirst($name);
|
||||
|
||||
if (Registry::has($unionName)) {
|
||||
return Registry::get($unionName);
|
||||
}
|
||||
|
||||
$types = [];
|
||||
foreach ($rule['type'] as $type) {
|
||||
$types[] = self::model(\ucfirst($type));
|
||||
}
|
||||
|
||||
$unionType = new UnionType([
|
||||
'name' => $unionName,
|
||||
'types' => $types,
|
||||
'resolveType' => static function ($object) use ($unionName) {
|
||||
return static::getUnionImplementation($unionName, $object);
|
||||
},
|
||||
]);
|
||||
|
||||
Registry::set($unionName, $unionType);
|
||||
|
||||
return $unionType;
|
||||
}
|
||||
|
||||
private static function getUnionImplementation(string $name, array $object): Type
|
||||
{
|
||||
// TODO: Find a better way to do this
|
||||
|
||||
switch ($name) {
|
||||
case 'Attributes':
|
||||
return static::getAttributeImplementation($object);
|
||||
case 'HashOptions':
|
||||
return static::getHashOptionsImplementation($object);
|
||||
}
|
||||
|
||||
throw new Exception('Unknown union type: ' . $name);
|
||||
}
|
||||
|
||||
private static function getAttributeImplementation(array $object): Type
|
||||
{
|
||||
switch ($object['type']) {
|
||||
case 'string':
|
||||
return match ($object['format'] ?? '') {
|
||||
'email' => static::model('AttributeEmail'),
|
||||
'url' => static::model('AttributeUrl'),
|
||||
'ip' => static::model('AttributeIp'),
|
||||
default => static::model('AttributeString'),
|
||||
};
|
||||
case 'integer':
|
||||
return static::model('AttributeInteger');
|
||||
case 'double':
|
||||
return static::model('AttributeFloat');
|
||||
case 'boolean':
|
||||
return static::model('AttributeBoolean');
|
||||
case 'datetime':
|
||||
return static::model('AttributeDatetime');
|
||||
}
|
||||
|
||||
throw new Exception('Unknown attribute implementation');
|
||||
}
|
||||
|
||||
private static function getHashOptionsImplementation(array $object): Type
|
||||
{
|
||||
switch ($object['type']) {
|
||||
case 'argon2':
|
||||
return static::model('AlgoArgon2');
|
||||
case 'bcrypt':
|
||||
return static::model('AlgoBcrypt');
|
||||
case 'md5':
|
||||
return static::model('AlgoMd5');
|
||||
case 'phpass':
|
||||
return static::model('AlgoPhpass');
|
||||
case 'scrypt':
|
||||
return static::model('AlgoScrypt');
|
||||
case 'scryptMod':
|
||||
return static::model('AlgoScryptModified');
|
||||
case 'sha':
|
||||
return static::model('AlgoSha');
|
||||
}
|
||||
|
||||
throw new Exception('Unknown hash options implementation');
|
||||
}
|
||||
}
|
||||
43
src/Appwrite/GraphQL/Types/Registry.php
Normal file
43
src/Appwrite/GraphQL/Types/Registry.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL\Types;
|
||||
|
||||
use GraphQL\Type\Definition\Type;
|
||||
|
||||
class Registry
|
||||
{
|
||||
private static array $register = [];
|
||||
|
||||
/**
|
||||
* Check if a type exists in the registry.
|
||||
*
|
||||
* @param string $type
|
||||
* @return bool
|
||||
*/
|
||||
public static function has(string $type): bool
|
||||
{
|
||||
return isset(self::$register[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type from the registry.
|
||||
*
|
||||
* @param string $type
|
||||
* @return Type
|
||||
*/
|
||||
public static function get(string $type): Type
|
||||
{
|
||||
return self::$register[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a type in the registry.
|
||||
*
|
||||
* @param string $type
|
||||
* @param Type $typeObject
|
||||
*/
|
||||
public static function set(string $type, Type $typeObject): void
|
||||
{
|
||||
self::$register[$type] = $typeObject;
|
||||
}
|
||||
}
|
||||
194
src/Appwrite/Promises/Promise.php
Normal file
194
src/Appwrite/Promises/Promise.php
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Promises;
|
||||
|
||||
abstract class Promise
|
||||
{
|
||||
protected const STATE_PENDING = 1;
|
||||
protected const STATE_FULFILLED = 0;
|
||||
protected const STATE_REJECTED = -1;
|
||||
|
||||
protected int $state = self::STATE_PENDING;
|
||||
|
||||
private mixed $result;
|
||||
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
if (\is_null($executor)) {
|
||||
return;
|
||||
}
|
||||
$resolve = function ($value) {
|
||||
$this->setResult($value);
|
||||
$this->setState(self::STATE_FULFILLED);
|
||||
};
|
||||
$reject = function ($value) {
|
||||
$this->setResult($value);
|
||||
$this->setState(self::STATE_REJECTED);
|
||||
};
|
||||
$this->execute($executor, $resolve, $reject);
|
||||
}
|
||||
|
||||
abstract protected function execute(
|
||||
callable $executor,
|
||||
callable $resolve,
|
||||
callable $reject
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Create a new promise from the given callable.
|
||||
*
|
||||
* @param callable $promise
|
||||
* @return self
|
||||
*/
|
||||
public static function create(callable $promise): self
|
||||
{
|
||||
return new static($promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve promise with given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
*/
|
||||
public static function resolve(mixed $value): self
|
||||
{
|
||||
return new static(function (callable $resolve) use ($value) {
|
||||
$resolve($value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects the promise with the given reason.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return self
|
||||
*/
|
||||
public static function reject(mixed $value): self
|
||||
{
|
||||
return new static(function (callable $resolve, callable $reject) use ($value) {
|
||||
$reject($value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch any exception thrown by the executor.
|
||||
*
|
||||
* @param callable $onRejected
|
||||
* @return self
|
||||
*/
|
||||
public function catch(callable $onRejected): self
|
||||
{
|
||||
return $this->then(null, $onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the promise.
|
||||
*
|
||||
* @param callable|null $onFulfilled
|
||||
* @param callable|null $onRejected
|
||||
* @return self
|
||||
*/
|
||||
public function then(
|
||||
?callable $onFulfilled = null,
|
||||
?callable $onRejected = null
|
||||
): self {
|
||||
if ($this->isRejected() && $onRejected === null) {
|
||||
return $this;
|
||||
}
|
||||
if ($this->isFulfilled() && $onFulfilled === null) {
|
||||
return $this;
|
||||
}
|
||||
return self::create(function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected) {
|
||||
while ($this->isPending()) {
|
||||
usleep(25000);
|
||||
}
|
||||
$callable = $this->isFulfilled() ? $onFulfilled : $onRejected;
|
||||
if (!\is_callable($callable)) {
|
||||
$resolve($this->result);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$resolve($callable($this->result));
|
||||
} catch (\Throwable $error) {
|
||||
$reject($error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that completes when all passed in promises complete.
|
||||
*
|
||||
* @param iterable|self[] $promises
|
||||
* @return self
|
||||
*/
|
||||
abstract public static function all(iterable $promises): self;
|
||||
|
||||
/**
|
||||
* Set resolved result
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
protected function setResult(mixed $value): void
|
||||
{
|
||||
if (!\is_callable([$value, 'then'])) {
|
||||
$this->result = $value;
|
||||
return;
|
||||
}
|
||||
|
||||
$resolved = false;
|
||||
|
||||
$callable = function ($value) use (&$resolved) {
|
||||
$this->setResult($value);
|
||||
$resolved = true;
|
||||
};
|
||||
|
||||
$value->then($callable, $callable);
|
||||
|
||||
while (!$resolved) {
|
||||
usleep(25000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change promise state
|
||||
*
|
||||
* @param integer $state
|
||||
* @return void
|
||||
*/
|
||||
protected function setState(int $state): void
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise is pending
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isPending(): bool
|
||||
{
|
||||
return $this->state == self::STATE_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise is fulfilled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isFulfilled(): bool
|
||||
{
|
||||
return $this->state == self::STATE_FULFILLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise is rejected
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isRejected(): bool
|
||||
{
|
||||
return $this->state == self::STATE_REJECTED;
|
||||
}
|
||||
}
|
||||
70
src/Appwrite/Promises/Swoole.php
Normal file
70
src/Appwrite/Promises/Swoole.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Promises;
|
||||
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
class Swoole extends Promise
|
||||
{
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
parent::__construct($executor);
|
||||
}
|
||||
|
||||
protected function execute(
|
||||
callable $executor,
|
||||
callable $resolve,
|
||||
callable $reject
|
||||
): void {
|
||||
\go(function () use ($executor, $resolve, $reject) {
|
||||
try {
|
||||
$executor($resolve, $reject);
|
||||
} catch (\Throwable $exception) {
|
||||
$reject($exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that completes when all passed in promises complete.
|
||||
*
|
||||
* @param iterable|Swoole[] $promises
|
||||
* @return Promise
|
||||
*/
|
||||
public static function all(iterable $promises): Promise
|
||||
{
|
||||
return self::create(function (callable $resolve, callable $reject) use ($promises) {
|
||||
$ticks = count($promises);
|
||||
|
||||
$result = [];
|
||||
$error = null;
|
||||
$channel = new Channel($ticks);
|
||||
$key = 0;
|
||||
|
||||
foreach ($promises as $promise) {
|
||||
$promise->then(function ($value) use ($key, &$result, $channel) {
|
||||
$result[$key] = $value;
|
||||
$channel->push(true);
|
||||
return $value;
|
||||
}, function ($err) use ($channel, &$error) {
|
||||
$channel->push(true);
|
||||
if ($error === null) {
|
||||
$error = $err;
|
||||
}
|
||||
});
|
||||
$key++;
|
||||
}
|
||||
while ($ticks--) {
|
||||
$channel->pop();
|
||||
}
|
||||
$channel->close();
|
||||
|
||||
if ($error !== null) {
|
||||
$reject($error);
|
||||
return;
|
||||
}
|
||||
|
||||
$resolve($result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -261,7 +261,12 @@ class Swagger2 extends Format
|
|||
|
||||
$bodyRequired = [];
|
||||
|
||||
foreach ($route->getParams() as $name => $param) { // Set params
|
||||
$parameters = \array_merge(
|
||||
$route->getParams(),
|
||||
$route->getLabel('sdk.parameters', []),
|
||||
);
|
||||
|
||||
foreach ($parameters as $name => $param) { // Set params
|
||||
/** @var \Utopia\Validator $validator */
|
||||
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
|
||||
|
||||
|
|
|
|||
|
|
@ -9,66 +9,37 @@ use Utopia\Swoole\Request as UtopiaRequest;
|
|||
|
||||
class Request extends UtopiaRequest
|
||||
{
|
||||
/**
|
||||
* @var Filter
|
||||
*/
|
||||
private static $filter = null;
|
||||
private static ?Filter $filter = null;
|
||||
private static ?Route $route = null;
|
||||
|
||||
/**
|
||||
* @var Route
|
||||
*/
|
||||
private static $route = null;
|
||||
|
||||
/**
|
||||
* Request constructor.
|
||||
*/
|
||||
public function __construct(SwooleRequest $request)
|
||||
{
|
||||
parent::__construct($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Params
|
||||
*
|
||||
* Get all params of current method
|
||||
*
|
||||
* @return array
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
$requestParameters = [];
|
||||
|
||||
switch ($this->getMethod()) {
|
||||
case self::METHOD_GET:
|
||||
$requestParameters = (!empty($this->swoole->get)) ? $this->swoole->get : [];
|
||||
break;
|
||||
case self::METHOD_POST:
|
||||
case self::METHOD_PUT:
|
||||
case self::METHOD_PATCH:
|
||||
case self::METHOD_DELETE:
|
||||
$requestParameters = $this->generateInput();
|
||||
break;
|
||||
default:
|
||||
$requestParameters = (!empty($this->swoole->get)) ? $this->swoole->get : [];
|
||||
}
|
||||
$parameters = parent::getParams();
|
||||
|
||||
if (self::hasFilter() && self::hasRoute()) {
|
||||
$endpointIdentifier = self::getRoute()->getLabel('sdk.namespace', 'unknown') . '.' . self::getRoute()->getLabel('sdk.method', 'unknown');
|
||||
$requestParameters = self::getFilter()->parse($requestParameters, $endpointIdentifier);
|
||||
$parameters = self::getFilter()->parse($parameters, $endpointIdentifier);
|
||||
}
|
||||
|
||||
return $requestParameters;
|
||||
return $parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function to set a response filter
|
||||
*
|
||||
* @param $filter the response filter to set
|
||||
* @param Filter|null $filter Filter the response filter to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setFilter(?Filter $filter)
|
||||
public static function setFilter(?Filter $filter): void
|
||||
{
|
||||
self::$filter = $filter;
|
||||
}
|
||||
|
|
@ -76,7 +47,7 @@ class Request extends UtopiaRequest
|
|||
/**
|
||||
* Return the currently set filter
|
||||
*
|
||||
* @return Filter
|
||||
* @return Filter|null
|
||||
*/
|
||||
public static function getFilter(): ?Filter
|
||||
{
|
||||
|
|
@ -96,19 +67,19 @@ class Request extends UtopiaRequest
|
|||
/**
|
||||
* Function to set a request route
|
||||
*
|
||||
* @param Route $route the request route to set
|
||||
* @param Route|null $route the request route to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setRoute(?Route $route)
|
||||
public static function setRoute(?Route $route): void
|
||||
{
|
||||
self::$route = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the currently get route
|
||||
* Return the current route
|
||||
*
|
||||
* @return Route
|
||||
* @return Route|null
|
||||
*/
|
||||
public static function getRoute(): ?Route
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Appwrite\Utopia;
|
||||
|
||||
use Exception;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Utopia\Swoole\Response as SwooleResponse;
|
||||
use Swoole\Http\Response as SwooleHTTPResponse;
|
||||
use Utopia\Database\Document;
|
||||
|
|
@ -84,6 +85,7 @@ use Appwrite\Utopia\Response\Model\UsageUsers;
|
|||
use Appwrite\Utopia\Response\Model\Variable;
|
||||
|
||||
/**
|
||||
* @method int getStatusCode()
|
||||
* @method Response setStatusCode(int $code = 200)
|
||||
*/
|
||||
class Response extends SwooleResponse
|
||||
|
|
@ -351,6 +353,7 @@ class Response extends SwooleResponse
|
|||
* HTTP content types
|
||||
*/
|
||||
public const CONTENT_TYPE_YAML = 'application/x-yaml';
|
||||
public const CONTENT_TYPE_NULL = 'null';
|
||||
|
||||
/**
|
||||
* List of defined output objects
|
||||
|
|
@ -372,7 +375,9 @@ class Response extends SwooleResponse
|
|||
/**
|
||||
* Get Model Object
|
||||
*
|
||||
* @param string $key
|
||||
* @return Model
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getModel(string $key): Model
|
||||
{
|
||||
|
|
@ -401,6 +406,7 @@ class Response extends SwooleResponse
|
|||
* @param string $model
|
||||
*
|
||||
* return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dynamic(Document $document, string $model): void
|
||||
{
|
||||
|
|
@ -411,7 +417,26 @@ class Response extends SwooleResponse
|
|||
$output = self::getFilter()->parse($output, $model);
|
||||
}
|
||||
|
||||
$this->json(!empty($output) ? $output : new \stdClass());
|
||||
switch ($this->getContentType()) {
|
||||
case self::CONTENT_TYPE_JSON:
|
||||
$this->json(!empty($output) ? $output : new \stdClass());
|
||||
break;
|
||||
|
||||
case self::CONTENT_TYPE_YAML:
|
||||
$this->yaml(!empty($output) ? $output : new \stdClass());
|
||||
break;
|
||||
|
||||
case self::CONTENT_TYPE_NULL:
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($model === self::MODEL_NONE) {
|
||||
$this->noContent();
|
||||
} else {
|
||||
$this->json(!empty($output) ? $output : new \stdClass());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -421,6 +446,8 @@ class Response extends SwooleResponse
|
|||
* @param string $model
|
||||
*
|
||||
* return array
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function output(Document $document, string $model): array
|
||||
{
|
||||
|
|
@ -520,6 +547,7 @@ class Response extends SwooleResponse
|
|||
* @param array $data
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function yaml(array $data): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ class AlgoArgon2 extends Model
|
|||
{
|
||||
// No options if imported. If hashed by Appwrite, following configuration is available:
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'argon2',
|
||||
'example' => 'argon2',
|
||||
])
|
||||
->addRule('memoryCost', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Memory used to compute hash.',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ class AlgoBcrypt extends Model
|
|||
public function __construct()
|
||||
{
|
||||
// No options, because this can only be imported, and verifying doesnt require any configuration
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'bcrypt',
|
||||
'example' => 'bcrypt',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ class AlgoMd5 extends Model
|
|||
public function __construct()
|
||||
{
|
||||
// No options, because this can only be imported, and verifying doesnt require any configuration
|
||||
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'md5',
|
||||
'example' => 'md5',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ class AlgoPhpass extends Model
|
|||
public function __construct()
|
||||
{
|
||||
// No options, because this can only be imported, and verifying doesnt require any configuration
|
||||
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'phpass',
|
||||
'example' => 'phpass',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ class AlgoScrypt extends Model
|
|||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'scrypt',
|
||||
'example' => 'scrypt',
|
||||
])
|
||||
->addRule('costCpu', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'CPU complexity of computed hash.',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ class AlgoScryptModified extends Model
|
|||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'scryptMod',
|
||||
'example' => 'scryptMod',
|
||||
])
|
||||
->addRule('salt', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Salt used to compute hash.',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ class AlgoSha extends Model
|
|||
public function __construct()
|
||||
{
|
||||
// No options, because this can only be imported, and verifying doesnt require any configuration
|
||||
|
||||
$this
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Algo type.',
|
||||
'default' => 'sha',
|
||||
'example' => 'sha',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
30
tests/benchmarks/graphql/account/graphql-full-selection.js
Normal file
30
tests/benchmarks/graphql/account/graphql-full-selection.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.post('http://localhost/v1/graphql', JSON.stringify({
|
||||
query: `query {
|
||||
accountGet {
|
||||
_id
|
||||
_createdAt
|
||||
_updatedAt
|
||||
name
|
||||
registration
|
||||
status
|
||||
passwordUpdate
|
||||
email
|
||||
phone
|
||||
emailVerification
|
||||
phoneVerification
|
||||
}
|
||||
}`
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
'Cookie': ''
|
||||
}
|
||||
})
|
||||
}
|
||||
16
tests/benchmarks/graphql/account/graphql-selection-set.js
Normal file
16
tests/benchmarks/graphql/account/graphql-selection-set.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.post('http://localhost/v1/graphql', JSON.stringify({
|
||||
query: `query { accountGet { _id email } }`
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
'Cookie': ''
|
||||
}
|
||||
})
|
||||
}
|
||||
14
tests/benchmarks/graphql/account/rest.js
Normal file
14
tests/benchmarks/graphql/account/rest.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.get('http://localhost/v1/account', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
'Cookie': ''
|
||||
}
|
||||
})
|
||||
}
|
||||
23
tests/benchmarks/graphql/locale/graphql-batched.js
Normal file
23
tests/benchmarks/graphql/locale/graphql-batched.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.post('http://localhost/v1/graphql', JSON.stringify([{
|
||||
query: 'query { localeGetCountries { total countries { code } } }',
|
||||
}, {
|
||||
query: 'query { localeGetContinents { total continents { code } } }',
|
||||
}, {
|
||||
query: 'query { localeGetCountriesEU { total continents { code } } }',
|
||||
}, {
|
||||
query: 'query { localeGetCountriesPhones { total continents { countryName } } }',
|
||||
}, {
|
||||
query: 'query { localeGetLanguages { total languages { name } } }',
|
||||
}]), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
}
|
||||
15
tests/benchmarks/graphql/locale/graphql-full-selection.js
Normal file
15
tests/benchmarks/graphql/locale/graphql-full-selection.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.post('http://localhost/v1/graphql', JSON.stringify({
|
||||
query: 'query { localeGetCountries { total countries { code name } } }',
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
}
|
||||
15
tests/benchmarks/graphql/locale/graphql-selection-set.js
Normal file
15
tests/benchmarks/graphql/locale/graphql-selection-set.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.post('http://localhost/v1/graphql', JSON.stringify({
|
||||
query: 'query { localeGetCountries { total } }',
|
||||
}), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
}
|
||||
37
tests/benchmarks/graphql/locale/rest-serial.js
Normal file
37
tests/benchmarks/graphql/locale/rest-serial.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.get('http://localhost/v1/locale/countries', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
http.get('http://localhost/v1/locale/continents', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
http.get('http://localhost/v1/locale/countries/eu', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
http.get('http://localhost/v1/locale/countries/phones', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
http.get('http://localhost/v1/locale/languages', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
}
|
||||
13
tests/benchmarks/graphql/locale/rest.js
Normal file
13
tests/benchmarks/graphql/locale/rest.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import http from 'k6/http';
|
||||
export const options = {
|
||||
vus: 20,
|
||||
duration: '60s',
|
||||
};
|
||||
export default function () {
|
||||
http.get('http://localhost/v1/locale/countries', {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Appwrite-Project': 'test',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -181,6 +181,10 @@ class Client
|
|||
$query = $this->flatten($params);
|
||||
break;
|
||||
|
||||
case 'application/graphql':
|
||||
$query = $params[0];
|
||||
break;
|
||||
|
||||
default:
|
||||
$query = http_build_query($params);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class HTTPTest extends Scope
|
|||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$this->assertEquals('Appwrite', $response['headers']['server']);
|
||||
$this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']);
|
||||
$this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, Content-Range, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('X-Fallback-Cookies', $response['headers']['access-control-expose-headers']);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
$this->assertEquals('true', $response['headers']['access-control-allow-credentials']);
|
||||
|
|
|
|||
|
|
@ -160,13 +160,14 @@ abstract class Scope extends TestCase
|
|||
'password' => $password,
|
||||
]);
|
||||
|
||||
$session = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
$token = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
self::$user[$this->getProject()['$id']] = [
|
||||
'$id' => ID::custom($user['body']['$id']),
|
||||
'name' => $user['body']['name'],
|
||||
'email' => $user['body']['email'],
|
||||
'session' => $session,
|
||||
'session' => $token,
|
||||
'sessionId' => $session['body']['$id'],
|
||||
];
|
||||
|
||||
return self::$user[$this->getProject()['$id']];
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Tests\E2E\Services\Account;
|
||||
|
||||
use Appwrite\SMS\Adapter\Mock;
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
|
|
@ -923,6 +924,7 @@ class AccountCustomClientTest extends Scope
|
|||
/**
|
||||
* @depends testUpdatePhone
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
public function testPhoneVerification(array $data): array
|
||||
{
|
||||
$session = $data['session'] ?? '';
|
||||
|
|
|
|||
184
tests/e2e/Services/GraphQL/AbuseTest.php
Normal file
184
tests/e2e/Services/GraphQL/AbuseTest.php
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\App;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class AbuseTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (App::getEnv('_APP_OPTIONS_ABUSE') === 'disabled') {
|
||||
$this->markTestSkipped('Abuse is not enabled.');
|
||||
}
|
||||
}
|
||||
|
||||
public function testRateLimitEnforced()
|
||||
{
|
||||
$data = $this->createCollection();
|
||||
$databaseId = $data['databaseId'];
|
||||
$collectionId = $data['collectionId'];
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DOCUMENT);
|
||||
$max = 120;
|
||||
|
||||
for ($i = 0; $i <= $max + 1; $i++) {
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'John Doe',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $gqlPayload);
|
||||
|
||||
if ($i < $max) {
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
} else {
|
||||
$this->assertArrayHasKey('errors', $response['body']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function testComplexQueryBlocked()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$COMPLEX_QUERY);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => 'user',
|
||||
'email' => 'user@appwrite.io',
|
||||
'password' => 'password',
|
||||
'databaseId' => 'database',
|
||||
'databaseName' => 'database',
|
||||
'collectionId' => 'collection',
|
||||
'collectionName' => 'collection',
|
||||
'collectionPermissions' => [
|
||||
Permission::read(Role::users()),
|
||||
Permission::create(Role::users()),
|
||||
Permission::update(Role::users()),
|
||||
Permission::delete(Role::users()),
|
||||
],
|
||||
'documentSecurity' => false,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$max = App::getEnv('_APP_GRAPHQL_MAX_QUERY_COMPLEXITY', 250);
|
||||
|
||||
$this->assertEquals('Max query complexity should be ' . $max . ' but got 259.', $response['body']['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testTooManyQueriesBlocked()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$maxQueries = App::getEnv('_APP_GRAPHQL_MAX_QUERIES', 10);
|
||||
|
||||
$query = [];
|
||||
for ($i = 0; $i <= $maxQueries + 1; $i++) {
|
||||
$query[] = ['query' => $this->getQuery(self::$LIST_COUNTRIES)];
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $query);
|
||||
|
||||
$this->assertEquals('Too many queries.', $response['body']['message']);
|
||||
}
|
||||
|
||||
private function createCollection()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DATABASE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => 'actors',
|
||||
'name' => 'AbuseDatabase',
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$databaseId = $response['body']['data']['databasesCreate']['_id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_COLLECTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => 'actors',
|
||||
'name' => 'Actors',
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::write(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$collectionId = $response['body']['data']['databasesCreateCollection']['_id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_STRING_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
'key' => 'name',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
sleep(2);
|
||||
|
||||
return [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
];
|
||||
}
|
||||
}
|
||||
507
tests/e2e/Services/GraphQL/AccountTest.php
Normal file
507
tests/e2e/Services/GraphQL/AccountTest.php
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class AccountTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use Base;
|
||||
|
||||
public function testCreateAccount(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_ACCOUNT);
|
||||
$email = 'test' . \rand() . '@test.com';
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'name' => 'User Name',
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
],
|
||||
];
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$account = $account['body']['data']['accountCreate'];
|
||||
$this->assertEquals('User Name', $account['name']);
|
||||
$this->assertEquals($email, $account['email']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testCreateAccountSession()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_ACCOUNT_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'email' => $this->getUser()['email'],
|
||||
'password' => 'password',
|
||||
]
|
||||
];
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $session['body']);
|
||||
$this->assertIsArray($session['body']['data']);
|
||||
$this->assertIsArray($session['body']['data']['accountCreateEmailSession']);
|
||||
|
||||
$cookie = $this->client->parseCookie((string)$session['headers']['set-cookie'])['a_session_' . $this->getProject()['$id']];
|
||||
$this->assertNotEmpty($cookie);
|
||||
}
|
||||
|
||||
public function testCreateMagicURLSession(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_MAGIC_URL);
|
||||
$email = 'test' . \rand() . '@test.com';
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
]
|
||||
];
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $session['body']);
|
||||
$this->assertIsArray($session['body']['data']);
|
||||
$this->assertIsArray($session['body']['data']['accountCreateMagicURLSession']);
|
||||
|
||||
return $session['body']['data']['accountCreateMagicURLSession'];
|
||||
}
|
||||
|
||||
public function testCreateEmailVerification(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_EMAIL_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'url' => 'http://localhost/verification'
|
||||
],
|
||||
];
|
||||
|
||||
$token = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $token['body']);
|
||||
$this->assertIsArray($token['body']['data']);
|
||||
$this->assertIsArray($token['body']['data']['accountCreateVerification']);
|
||||
|
||||
return $token['body']['data']['accountCreateVerification'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateAccountPhone
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreatePhoneVerification(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_PHONE_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$token = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $token['body']);
|
||||
$this->assertIsArray($token['body']['data']);
|
||||
$this->assertIsArray($token['body']['data']['accountCreatePhoneVerification']);
|
||||
|
||||
return $token['body']['data']['accountCreatePhoneVerification'];
|
||||
}
|
||||
|
||||
public function testCreatePasswordRecovery(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_PASSWORD_RECOVERY);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'email' => $this->getUser()['email'],
|
||||
'url' => 'http://localhost/recovery',
|
||||
],
|
||||
];
|
||||
|
||||
$token = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $token['body']);
|
||||
$this->assertIsArray($token['body']['data']);
|
||||
$this->assertIsArray($token['body']['data']['accountCreateRecovery']);
|
||||
|
||||
return $token['body']['data']['accountCreateRecovery'];
|
||||
}
|
||||
|
||||
public function testCreateAnonymousSession(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_ANONYMOUS_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $session['body']);
|
||||
$this->assertIsArray($session['body']['data']);
|
||||
$this->assertIsArray($session['body']['data']['accountCreateAnonymousSession']);
|
||||
|
||||
return $session['body']['data']['accountCreateAnonymousSession'];
|
||||
}
|
||||
|
||||
public function testCreateAccountJWT(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_ACCOUNT_JWT);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
$jwt = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($jwt['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $jwt['body']);
|
||||
$this->assertNotEmpty($jwt['body']['data']['accountCreateJWT']['jwt']);
|
||||
|
||||
return $jwt;
|
||||
}
|
||||
|
||||
public function testGetAccount(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ACCOUNT);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
|
||||
$account = $account['body']['data']['accountGet'];
|
||||
$this->assertEquals('User Name', $account['name']);
|
||||
$this->assertEquals($this->getUser()['email'], $account['email']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testGetAccountPreferences(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ACCOUNT_PREFS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$prefs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $prefs['body']);
|
||||
$this->assertIsArray($prefs['body']['data']);
|
||||
$this->assertEquals('{}', $prefs['body']['data']['accountGetPrefs']['data']);
|
||||
|
||||
return $prefs;
|
||||
}
|
||||
|
||||
public function testGetAccountSessions(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ACCOUNT_SESSIONS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$sessions = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $sessions['body']);
|
||||
$this->assertIsArray($sessions['body']['data']);
|
||||
$this->assertIsArray($sessions['body']['data']['accountListSessions']);
|
||||
|
||||
return $sessions;
|
||||
}
|
||||
|
||||
public function testGetAccountSession(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ACCOUNT_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'sessionId' => 'current',
|
||||
]
|
||||
];
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $session['body']);
|
||||
$this->assertIsArray($session['body']['data']);
|
||||
$this->assertIsArray($session['body']['data']['accountGetSession']);
|
||||
$this->assertEquals($this->getUser()['sessionId'], $session['body']['data']['accountGetSession']['_id']);
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
public function testGetAccountLogs(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ACCOUNT_LOGS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$logs = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $logs['body']);
|
||||
$this->assertIsArray($logs['body']['data']);
|
||||
$this->assertIsArray($logs['body']['data']['accountListLogs']);
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
public function testUpdateAccountName(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_NAME);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'name' => 'Tester Updated'
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdateName']);
|
||||
$this->assertEquals('Tester Updated', $account['body']['data']['accountUpdateName']['name']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testUpdateAccountEmail(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_EMAIL);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'email' => 'newemail@appwrite.io',
|
||||
'password' => 'password',
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdateEmail']);
|
||||
$this->assertEquals('newemail@appwrite.io', $account['body']['data']['accountUpdateEmail']['email']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testUpdateAccountPassword(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_PASSWORD);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'oldPassword' => 'password',
|
||||
'password' => 'password',
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdatePassword']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testUpdateAccountPhone(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_PHONE);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'phone' => '+123456789',
|
||||
'password' => 'password',
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdatePhone']);
|
||||
$this->assertEquals('+123456789', $account['body']['data']['accountUpdatePhone']['phone']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testUpdateAccountStatus(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_STATUS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdateStatus']);
|
||||
$this->assertEquals(false, $account['body']['data']['accountUpdateStatus']['status']);
|
||||
|
||||
unset(self::$user[$this->getProject()['$id']]);
|
||||
$this->getUser();
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testUpdateAccountPrefs(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_ACCOUNT_PREFS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'prefs' => [
|
||||
'key' => 'value'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $account['body']);
|
||||
$this->assertIsArray($account['body']['data']);
|
||||
$this->assertIsArray($account['body']['data']['accountUpdatePrefs']);
|
||||
$this->assertEquals(['data' => \json_encode(['key' => 'value'])], $account['body']['data']['accountUpdatePrefs']['prefs']);
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testDeleteAccountSessions(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_ACCOUNT_SESSIONS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($account['body']);
|
||||
$this->assertEquals(204, $account['headers']['status-code']);
|
||||
|
||||
unset(self::$user[$this->getProject()['$id']]);
|
||||
$this->getUser();
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
public function testDeleteAccountSession(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_ACCOUNT_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'sessionId' => 'current',
|
||||
]
|
||||
];
|
||||
|
||||
$account = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($account['body']);
|
||||
$this->assertEquals(204, $account['headers']['status-code']);
|
||||
|
||||
unset(self::$user[$this->getProject()['$id']]);
|
||||
$this->getUser();
|
||||
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
256
tests/e2e/Services/GraphQL/AuthTest.php
Normal file
256
tests/e2e/Services/GraphQL/AuthTest.php
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
use Utopia\Database\Permission;
|
||||
|
||||
class AuthTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use Base;
|
||||
|
||||
private array $account1;
|
||||
private array $account2;
|
||||
|
||||
private string $token1;
|
||||
private string $token2;
|
||||
|
||||
private array $database;
|
||||
private array $collection;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_ACCOUNT);
|
||||
|
||||
$email1 = 'test' . \rand() . '@test.com';
|
||||
$email2 = 'test' . \rand() . '@test.com';
|
||||
|
||||
// Create account 1
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'name' => 'User Name',
|
||||
'email' => $email1,
|
||||
'password' => 'password',
|
||||
],
|
||||
];
|
||||
$this->account1 = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
// Create account 2
|
||||
$graphQLPayload['variables']['userId'] = ID::unique();
|
||||
$graphQLPayload['variables']['email'] = $email2;
|
||||
|
||||
$account2 = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
// Create session 1
|
||||
$query = $this->getQuery(self::$CREATE_ACCOUNT_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'email' => $email1,
|
||||
'password' => 'password',
|
||||
]
|
||||
];
|
||||
$session1 = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->token1 = $this->client->parseCookie(
|
||||
(string)$session1['headers']['set-cookie']
|
||||
)['a_session_' . $projectId];
|
||||
|
||||
// Create session 2
|
||||
$graphQLPayload['variables']['email'] = $email2;
|
||||
|
||||
$session2 = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->token2 = $this->client->parseCookie(
|
||||
(string)$session2['headers']['set-cookie']
|
||||
)['a_session_' . $projectId];
|
||||
|
||||
// Create database
|
||||
$query = $this->getQuery(self::$CREATE_DATABASE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
]
|
||||
];
|
||||
$this->database = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
// Create collection
|
||||
$query = $this->getQuery(self::$CREATE_COLLECTION);
|
||||
$userId = $this->account1['body']['data']['accountCreate']['_id'];
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
'documentSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::create(Role::user($userId))
|
||||
]
|
||||
]
|
||||
];
|
||||
$this->collection = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
// Create string attribute
|
||||
$query = $this->getQuery(self::$CREATE_STRING_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => $this->collection['body']['data']['databasesCreateCollection']['_id'],
|
||||
'key' => 'name',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]
|
||||
];
|
||||
$this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
public function testInvalidAuth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
// Create document as account 1
|
||||
$query = $this->getQuery(self::$CREATE_DOCUMENT);
|
||||
$userId = $this->account1['body']['data']['accountCreate']['_id'];
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => $this->collection['body']['data']['databasesCreateCollection']['_id'],
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'John Doe',
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
]
|
||||
]
|
||||
];
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->token1,
|
||||
], $gqlPayload);
|
||||
|
||||
// Try to read as account 1
|
||||
$query = $this->getQuery(self::$GET_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => $this->collection['body']['data']['databasesCreateCollection']['_id'],
|
||||
'documentId' => $document['body']['data']['databasesCreateDocument']['_id'],
|
||||
]
|
||||
];
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->token1,
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($document['body']['data']['databasesGetDocument']);
|
||||
$this->assertArrayNotHasKey('errors', $document['body']);
|
||||
|
||||
// Try to read as account 2
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->token2,
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertArrayHasKey('errors', $document['body']);
|
||||
$this->assertEquals('Document with the requested ID could not be found.', $document['body']['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testValidAuth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
// Create document as account 1
|
||||
$query = $this->getQuery(self::$CREATE_DOCUMENT);
|
||||
$userId = $this->account1['body']['data']['accountCreate']['_id'];
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => $this->collection['body']['data']['databasesCreateCollection']['_id'],
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'John Doe',
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::user($userId)),
|
||||
Permission::update(Role::user($userId)),
|
||||
Permission::delete(Role::user($userId)),
|
||||
],
|
||||
]
|
||||
];
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->token1,
|
||||
], $gqlPayload);
|
||||
|
||||
// Try to delete as account 1
|
||||
$query = $this->getQuery(self::$DELETE_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $this->database['body']['data']['databasesCreate']['_id'],
|
||||
'collectionId' => $this->collection['body']['data']['databasesCreateCollection']['_id'],
|
||||
'documentId' => $document['body']['data']['databasesCreateDocument']['_id'],
|
||||
]
|
||||
];
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $this->token1,
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($document['body']);
|
||||
$this->assertEquals(204, $document['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
162
tests/e2e/Services/GraphQL/AvatarsTest.php
Normal file
162
tests/e2e/Services/GraphQL/AvatarsTest.php
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class AvatarsTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testGetCreditCardIcon()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_CREDIT_CARD_ICON);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'code' => 'visa',
|
||||
],
|
||||
];
|
||||
|
||||
$creditCardIcon = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(18767, \strlen($creditCardIcon['body']));
|
||||
|
||||
return $creditCardIcon['body'];
|
||||
}
|
||||
|
||||
public function testGetBrowserIcon()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_BROWSER_ICON);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'code' => 'ff',
|
||||
],
|
||||
];
|
||||
|
||||
$browserIcon = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(11100, \strlen($browserIcon['body']));
|
||||
|
||||
return $browserIcon['body'];
|
||||
}
|
||||
|
||||
public function testGetCountryFlag()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_COUNTRY_FLAG);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'code' => 'us',
|
||||
],
|
||||
];
|
||||
|
||||
$countryFlag = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(7460, \strlen($countryFlag['body']));
|
||||
|
||||
return $countryFlag['body'];
|
||||
}
|
||||
|
||||
public function testGetImageFromURL()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_IMAGE_FROM_URL);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'url' => 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
],
|
||||
];
|
||||
|
||||
$image = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(36036, \strlen($image['body']));
|
||||
|
||||
return $image['body'];
|
||||
}
|
||||
|
||||
public function testGetFavicon()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FAVICON);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'url' => 'https://www.google.com/',
|
||||
],
|
||||
];
|
||||
|
||||
$favicon = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(5430, \strlen($favicon['body']));
|
||||
|
||||
return $favicon['body'];
|
||||
}
|
||||
|
||||
public function testGetQRCode()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_QRCODE);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'text' => 'https://www.google.com/',
|
||||
],
|
||||
];
|
||||
|
||||
$qrCode = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(14771, \strlen($qrCode['body']));
|
||||
|
||||
return $qrCode['body'];
|
||||
}
|
||||
|
||||
public function testGetInitials()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_INITIALS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'name' => 'John Doe',
|
||||
],
|
||||
];
|
||||
|
||||
$initials = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals(4959, \strlen($initials['body']));
|
||||
|
||||
return $initials['body'];
|
||||
}
|
||||
}
|
||||
1813
tests/e2e/Services/GraphQL/Base.php
Normal file
1813
tests/e2e/Services/GraphQL/Base.php
Normal file
File diff suppressed because it is too large
Load diff
383
tests/e2e/Services/GraphQL/BatchTest.php
Normal file
383
tests/e2e/Services/GraphQL/BatchTest.php
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class BatchTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
public function testArrayBatchedQueries()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query1 = 'query { localeListCountries { total countries { code } } }';
|
||||
$query2 = 'query { localeListContinents { total continents { code } } }';
|
||||
$graphQLPayload = [
|
||||
['query' => $query1],
|
||||
['query' => $query2],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('localeListContinents', $response['body'][1]['data']);
|
||||
$this->assertEquals(194, $response['body'][0]['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(7, $response['body'][1]['data']['localeListContinents']['total']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedQueriesOfSameType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = 'query { localeListCountries { total countries { code } } }';
|
||||
$graphQLPayload = [
|
||||
['query' => $query],
|
||||
['query' => $query],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][1]['data']);
|
||||
$this->assertEquals(194, $response['body'][0]['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(194, $response['body'][1]['data']['localeListCountries']['total']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedMutations()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email = 'tester' . \uniqid() . '@example.com';
|
||||
$graphQLPayload = [[
|
||||
'query' => 'mutation CreateAccount($userId: String!, $email: String!, $password: String!, $name: String) {
|
||||
accountCreate(userId: $userId, email: $email, password: $password, name: $name) {
|
||||
name
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester 1',
|
||||
],
|
||||
],
|
||||
[
|
||||
'query' => 'mutation CreateTeam($teamId: String! $name: String!) {
|
||||
teamsCreate(teamId: $teamId, name: $name) {
|
||||
name
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Team 1',
|
||||
],
|
||||
]];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('teamsCreate', $response['body'][1]['data']);
|
||||
$this->assertEquals('Tester 1', $response['body'][0]['data']['accountCreate']['name']);
|
||||
$this->assertEquals('Team 1', $response['body'][1]['data']['teamsCreate']['name']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedMutationsOfSameType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email1 = 'tester' . \uniqid() . '@example.com';
|
||||
$email2 = 'tester' . \uniqid() . '@example.com';
|
||||
$query = 'mutation CreateAccount($userId: String!, $email: String!, $password: String!, $name: String) {
|
||||
accountCreate(userId: $userId, email: $email, password: $password, name: $name) {
|
||||
_id
|
||||
}
|
||||
}';
|
||||
|
||||
$graphQLPayload = [
|
||||
[
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email1,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester 1',
|
||||
],
|
||||
],
|
||||
[
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email2,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester 2',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body'][1]['data']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedMixed()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email = 'tester' . \uniqid() . '@example.com';
|
||||
$graphQLPayload = [
|
||||
['query' => 'query { localeListCountries { total countries { code } } }'],
|
||||
['query' => 'query { localeListContinents { total continents { code } } }'],
|
||||
[
|
||||
'query' => 'mutation CreateAccount($userId: String!, $email: String!, $password: String!, $name: String) {
|
||||
accountCreate(userId: $userId, email: $email, password: $password, name: $name) {
|
||||
name
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester 1',
|
||||
]
|
||||
],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertIsArray($response['body'][2]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][2]);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('localeListContinents', $response['body'][1]['data']);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body'][2]['data']);
|
||||
$this->assertEquals(194, $response['body'][0]['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(7, $response['body'][1]['data']['localeListContinents']['total']);
|
||||
$this->assertEquals('Tester 1', $response['body'][2]['data']['accountCreate']['name']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedMixedOfSameType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email = 'tester' . \uniqid() . '@example.com';
|
||||
$query = 'query { localeListCountries { total countries { code } } }';
|
||||
$graphQLPayload = [
|
||||
['query' => $query],
|
||||
['query' => $query],
|
||||
[
|
||||
'query' => 'mutation CreateAccount($userId: String!, $email: String!, $password: String!, $name: String) {
|
||||
accountCreate(userId: $userId, email: $email, password: $password, name: $name) {
|
||||
_id
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester 1',
|
||||
]
|
||||
],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertIsArray($response['body'][2]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][2]);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][1]['data']);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body'][2]['data']);
|
||||
$this->assertEquals(194, $response['body'][0]['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(194, $response['body'][1]['data']['localeListCountries']['total']);
|
||||
$this->assertArrayHasKey('_id', $response['body'][2]['data']['accountCreate']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedQueries()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = '
|
||||
query {
|
||||
localeListCountries { total countries { code } }
|
||||
localeListContinents { total continents { code } }
|
||||
}
|
||||
';
|
||||
$graphQLPayload = [
|
||||
['query' => $query],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body']['data']);
|
||||
$this->assertArrayHasKey('localeListContinents', $response['body']['data']);
|
||||
$this->assertEquals(194, $response['body']['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(7, $response['body']['data']['localeListContinents']['total']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedQueriesOfSameType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = '
|
||||
query {
|
||||
localeListCountries { total countries { code } }
|
||||
localeListCountries { total countries { code } }
|
||||
}
|
||||
';
|
||||
$graphQLPayload = [
|
||||
['query' => $query],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body']['data']);
|
||||
$this->assertEquals(194, $response['body']['data']['localeListCountries']['total']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedMutations()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email = 'tester' . \uniqid() . '@example.com';
|
||||
$graphQLPayload = [
|
||||
'query' => 'mutation CreateAndLogin($userId: String!, $email: String!, $password: String!, $name: String) {
|
||||
accountCreate(userId: $userId, email: $email, password: $password, name: $name) {
|
||||
name
|
||||
}
|
||||
accountCreateEmailSession(email: $email, password: $password) {
|
||||
expire
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Tester',
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$this->assertArrayHasKey('accountCreate', $response['body']['data']);
|
||||
$this->assertArrayHasKey('accountCreateEmailSession', $response['body']['data']);
|
||||
$this->assertEquals('Tester', $response['body']['data']['accountCreate']['name']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedMutationsOfSameType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email1 = 'tester' . \uniqid() . '@example.com';
|
||||
$email2 = 'tester' . \uniqid() . '@example.com';
|
||||
$graphQLPayload = [
|
||||
'query' => 'mutation CreateAndLogin($email1: String!, $email2: String!, $password: String!, $name1: String, $name2: String) {
|
||||
accountCreate(userId: "unique()", email: $email1, password: $password, name: $name1) {
|
||||
name
|
||||
}
|
||||
accountCreate(userId: "unique()", email: $email2, password: $password, name: $name2) {
|
||||
name
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email1' => $email1,
|
||||
'email2' => $email2,
|
||||
'name1' => 'Tester 1',
|
||||
'name2' => 'Tester 2',
|
||||
'password' => 'password',
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertEquals('Fields "accountCreate" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.', $response['body']['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedMutationsOfSameTypeWithAlias()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$email1 = 'tester' . \uniqid() . '@example.com';
|
||||
$email2 = 'tester' . \uniqid() . '@example.com';
|
||||
$graphQLPayload = [
|
||||
'query' => 'mutation CreateAndLogin($email1: String!, $email2: String!, $password: String!, $name1: String, $name2: String) {
|
||||
account1: accountCreate(userId: "unique()", email: $email1, password: $password, name: $name1) {
|
||||
name
|
||||
}
|
||||
account2: accountCreate(userId: "unique()", email: $email2, password: $password, name: $name2) {
|
||||
name
|
||||
}
|
||||
}',
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email1' => $email1,
|
||||
'email2' => $email2,
|
||||
'name1' => 'Tester 1',
|
||||
'name2' => 'Tester 2',
|
||||
'password' => 'password',
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$this->assertArrayHasKey('account1', $response['body']['data']);
|
||||
$this->assertArrayHasKey('account2', $response['body']['data']);
|
||||
$this->assertEquals('Tester 1', $response['body']['data']['account1']['name']);
|
||||
$this->assertEquals('Tester 2', $response['body']['data']['account2']['name']);
|
||||
}
|
||||
}
|
||||
224
tests/e2e/Services/GraphQL/ContentTypeTest.php
Normal file
224
tests/e2e/Services/GraphQL/ContentTypeTest.php
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class ContentTypeTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testGraphQLContentType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = 'query { localeListCountries { total countries { code } } }';
|
||||
$graphQLPayload = [$query]; // Needs to be an array because the test client expects it
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/graphql',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$response = $response['body']['data']['localeListCountries'];
|
||||
$this->assertEquals(194, $response['total']);
|
||||
}
|
||||
|
||||
public function testSingleQueryJSONContentType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = 'query { localeListCountries { total countries { code } } }';
|
||||
$graphQLPayload = ['query' => $query];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$response = $response['body']['data']['localeListCountries'];
|
||||
$this->assertEquals(194, $response['total']);
|
||||
}
|
||||
|
||||
public function testArrayBatchedJSONContentType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query1 = 'query { localeListCountries { total countries { code } } }';
|
||||
$query2 = 'query { localeListContinents { total continents { code } } }';
|
||||
$graphQLPayload = [
|
||||
['query' => $query1],
|
||||
['query' => $query2],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body'][0]['data']);
|
||||
$this->assertIsArray($response['body'][1]['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][0]);
|
||||
$this->assertArrayNotHasKey('errors', $response['body'][1]);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body'][0]['data']);
|
||||
$this->assertArrayHasKey('localeListContinents', $response['body'][1]['data']);
|
||||
$this->assertEquals(194, $response['body'][0]['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(7, $response['body'][1]['data']['localeListContinents']['total']);
|
||||
}
|
||||
|
||||
public function testQueryBatchedJSONContentType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = '
|
||||
query {
|
||||
localeListCountries { total countries { code } }
|
||||
localeListContinents { total continents { code } }
|
||||
}
|
||||
';
|
||||
$graphQLPayload = [
|
||||
['query' => $query],
|
||||
];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($response['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $response['body']);
|
||||
$this->assertArrayHasKey('localeListCountries', $response['body']['data']);
|
||||
$this->assertArrayHasKey('localeListContinents', $response['body']['data']);
|
||||
$this->assertEquals(194, $response['body']['data']['localeListCountries']['total']);
|
||||
$this->assertEquals(7, $response['body']['data']['localeListContinents']['total']);
|
||||
}
|
||||
|
||||
public function testMultipartFormDataContentType()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => ID::unique(),
|
||||
'name' => 'Test Bucket',
|
||||
'fileSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$bucket = $bucket['body']['data']['storageCreateBucket'];
|
||||
|
||||
$query = $this->getQuery(self::$CREATE_FILE);
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'fileId' => ID::unique(),
|
||||
'file' => null,
|
||||
'fileSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
]),
|
||||
'map' => \json_encode([
|
||||
'file' => ["variables.file"]
|
||||
]),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
$this->assertIsArray($file['body']['data']['storageCreateFile']);
|
||||
}
|
||||
|
||||
public function testPostNoBody()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
|
||||
public function testPostEmptyBody()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
|
||||
public function testPostRandomBody()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), ['foo' => 'bar']);
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
|
||||
public function testGetNoQuery()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_GET, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
|
||||
public function testGetEmptyQuery()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_GET, '/graphql?query=', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
|
||||
public function testGetRandomParameters()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$response = $this->client->call(Client::METHOD_GET, '/graphql?random=random', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals('Param "query" is not optional.', $response['body']['message']);
|
||||
}
|
||||
}
|
||||
305
tests/e2e/Services/GraphQL/DatabaseClientTest.php
Normal file
305
tests/e2e/Services/GraphQL/DatabaseClientTest.php
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class DatabaseClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use Base;
|
||||
|
||||
public function testCreateDatabase(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DATABASE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
]
|
||||
];
|
||||
|
||||
$database = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($database['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $database['body']);
|
||||
$database = $database['body']['data']['databasesCreate'];
|
||||
$this->assertEquals('Actors', $database['name']);
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDatabase
|
||||
*/
|
||||
public function testCreateCollection($database): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_COLLECTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $database['_id'],
|
||||
'collectionId' => 'actors',
|
||||
'name' => 'Actors',
|
||||
'documentSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::users()),
|
||||
Permission::update(Role::users()),
|
||||
Permission::delete(Role::users()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($collection['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $collection['body']);
|
||||
$collection = $collection['body']['data']['databasesCreateCollection'];
|
||||
$this->assertEquals('Actors', $collection['name']);
|
||||
|
||||
return [
|
||||
'database' => $database,
|
||||
'collection' => $collection,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
public function testCreateStringAttribute($data): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_STRING_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'name',
|
||||
'size' => 256,
|
||||
'required' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $attribute['body']);
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesCreateStringAttribute']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
*/
|
||||
public function testCreateIntegerAttribute($data): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_INTEGER_ATTRIBUTE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'key' => 'age',
|
||||
'min' => 18,
|
||||
'max' => 150,
|
||||
'required' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$attribute = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $attribute['body']);
|
||||
$this->assertIsArray($attribute['body']['data']);
|
||||
$this->assertIsArray($attribute['body']['data']['databasesCreateIntegerAttribute']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateStringAttribute
|
||||
* @depends testCreateIntegerAttribute
|
||||
*/
|
||||
public function testCreateDocument($data): array
|
||||
{
|
||||
sleep(1);
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'John Doe',
|
||||
'age' => 35,
|
||||
],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $document['body']);
|
||||
$this->assertIsArray($document['body']['data']);
|
||||
|
||||
$document = $document['body']['data']['databasesCreateDocument'];
|
||||
$this->assertIsArray($document);
|
||||
|
||||
return [
|
||||
'database' => $data['database'],
|
||||
'collection' => $data['collection'],
|
||||
'document' => $document,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateCollection
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDocuments($data): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_DOCUMENTS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$documents = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $documents['body']);
|
||||
$this->assertIsArray($documents['body']['data']);
|
||||
$this->assertIsArray($documents['body']['data']['databasesListDocuments']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDocument($data): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'documentId' => $data['document']['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $document['body']);
|
||||
$this->assertIsArray($document['body']['data']);
|
||||
$this->assertIsArray($document['body']['data']['databasesGetDocument']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testUpdateDocument($data): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'documentId' => $data['document']['_id'],
|
||||
'data' => [
|
||||
'name' => 'New Document Name',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertArrayNotHasKey('errors', $document['body']);
|
||||
$this->assertIsArray($document['body']['data']);
|
||||
$document = $document['body']['data']['databasesUpdateDocument'];
|
||||
$this->assertIsArray($document);
|
||||
|
||||
$this->assertStringContainsString('New Document Name', $document['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateDocument
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteDocument($data): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_DOCUMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => $data['database']['_id'],
|
||||
'collectionId' => $data['collection']['_id'],
|
||||
'documentId' => $data['document']['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$document = $this->client->call(Client::METHOD_POST, '/graphql', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($document['body']);
|
||||
$this->assertEquals(204, $document['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
1040
tests/e2e/Services/GraphQL/DatabaseServerTest.php
Normal file
1040
tests/e2e/Services/GraphQL/DatabaseServerTest.php
Normal file
File diff suppressed because it is too large
Load diff
207
tests/e2e/Services/GraphQL/FunctionsClientTest.php
Normal file
207
tests/e2e/Services/GraphQL/FunctionsClientTest.php
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class FunctionsClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use Base;
|
||||
|
||||
public function testCreateFunction(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_FUNCTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test Function',
|
||||
'runtime' => 'php-8.0',
|
||||
'execute' => [Role::any()->toString()],
|
||||
]
|
||||
];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
|
||||
$this->assertIsArray($function['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $function['body']);
|
||||
|
||||
$function = $function['body']['data']['functionsCreate'];
|
||||
$functionId = $function['_id'];
|
||||
|
||||
$query = '
|
||||
mutation createVariables($functionId: String!) {
|
||||
var1: functionsCreateVariable(functionId: $functionId, key: "name", value: "John Doe") {
|
||||
_id
|
||||
}
|
||||
var2: functionsCreateVariable(functionId: $functionId, key: "age", value: "42") {
|
||||
_id
|
||||
}
|
||||
}
|
||||
';
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $functionId,
|
||||
]
|
||||
];
|
||||
|
||||
$variables = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($variables['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $variables['body']);
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateDeployment($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'entrypoint' => 'index.php',
|
||||
'activate' => true,
|
||||
'code' => null,
|
||||
]
|
||||
]),
|
||||
'map' => \json_encode([
|
||||
'code' => ["variables.code"]
|
||||
]),
|
||||
'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
|
||||
];
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($deployment['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $deployment['body']);
|
||||
|
||||
sleep(15);
|
||||
|
||||
return $deployment['body']['data']['functionsCreateDeployment'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testCreateDeployment
|
||||
* @param $function
|
||||
* @param $deployment
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateExecution($function, $deployment): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_EXECUTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
return $execution['body']['data']['functionsCreateExecution'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetExecutions($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_EXECUTIONS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$executions = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($executions['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $executions['body']);
|
||||
$executions = $executions['body']['data']['functionsListExecutions'];
|
||||
$this->assertIsArray($executions);
|
||||
|
||||
return $executions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testCreateExecution
|
||||
* @param $function
|
||||
* @param $execution
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetExecution($function, $execution): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_EXECUTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'executionId' => $execution['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
$execution = $execution['body']['data']['functionsGetExecution'];
|
||||
$this->assertIsArray($execution);
|
||||
|
||||
return $execution;
|
||||
}
|
||||
}
|
||||
458
tests/e2e/Services/GraphQL/FunctionsServerTest.php
Normal file
458
tests/e2e/Services/GraphQL/FunctionsServerTest.php
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class FunctionsServerTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testCreateFunction(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_FUNCTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test Function',
|
||||
'runtime' => 'php-8.0',
|
||||
'execute' => [Role::any()->toString()],
|
||||
]
|
||||
];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($function['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $function['body']);
|
||||
|
||||
$function = $function['body']['data']['functionsCreate'];
|
||||
$functionId = $function['_id'];
|
||||
|
||||
$query = '
|
||||
mutation createVariables($functionId: String!) {
|
||||
var1: functionsCreateVariable(functionId: $functionId, key: "name", value: "John Doe") {
|
||||
_id
|
||||
}
|
||||
var2: functionsCreateVariable(functionId: $functionId, key: "age", value: "42") {
|
||||
_id
|
||||
}
|
||||
}
|
||||
';
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $functionId,
|
||||
]
|
||||
];
|
||||
|
||||
$variables = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($variables['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $variables['body']);
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateDeployment($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_DEPLOYMENT);
|
||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php/code.tar.gz";
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'entrypoint' => 'index.php',
|
||||
'activate' => true,
|
||||
'code' => null,
|
||||
]
|
||||
]),
|
||||
'map' => \json_encode([
|
||||
'code' => ["variables.code"]
|
||||
]),
|
||||
'code' => new CURLFile($code, 'application/gzip', 'code.tar.gz'),
|
||||
];
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($deployment['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $deployment['body']);
|
||||
|
||||
sleep(15);
|
||||
|
||||
return $deployment['body']['data']['functionsCreateDeployment'];
|
||||
}
|
||||
|
||||
/**
|
||||
* * @depends testCreateFunction
|
||||
* @depends testCreateDeployment
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateExecution($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_EXECUTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
|
||||
return $execution['body']['data']['functionsCreateExecution'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testGetDeployment
|
||||
* @param $function
|
||||
* @param $deployment
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateRetryBuild($function, $deployment): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$RETRY_BUILD);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'deploymentId' => $deployment['_id'],
|
||||
'buildId' => $deployment['buildId'],
|
||||
]
|
||||
];
|
||||
|
||||
$retryBuild = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($retryBuild['body']['errors']);
|
||||
$this->assertEquals("Build not failed", $retryBuild['body']['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testGetFunctions(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FUNCTIONS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$functions = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($functions['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $functions['body']);
|
||||
$functions = $functions['body']['data']['functionsList'];
|
||||
$this->assertIsArray($functions);
|
||||
|
||||
return $functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFunction($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FUNCTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($function['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $function['body']);
|
||||
$function = $function['body']['data']['functionsGet'];
|
||||
$this->assertIsArray($function);
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
public function testGetRuntimes(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_RUNTIMES);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$runtimes = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($runtimes['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $runtimes['body']);
|
||||
$runtimes = $runtimes['body']['data']['functionsListRuntimes'];
|
||||
$this->assertIsArray($runtimes);
|
||||
|
||||
return $runtimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDeployments($function)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_DEPLOYMENTS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$deployments = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($deployments['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $deployments['body']);
|
||||
$deployments = $deployments['body']['data']['functionsListDeployments'];
|
||||
$this->assertIsArray($deployments);
|
||||
|
||||
return $deployments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testCreateDeployment
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetDeployment($function, $deployment)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_DEPLOYMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'deploymentId' => $deployment['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$deployment = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($deployment['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $deployment['body']);
|
||||
$deployment = $deployment['body']['data']['functionsGetDeployment'];
|
||||
$this->assertIsArray($deployment);
|
||||
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetExecutions($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_EXECUTIONS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$executions = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($executions['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $executions['body']);
|
||||
$executions = $executions['body']['data']['functionsListExecutions'];
|
||||
$this->assertIsArray($executions);
|
||||
|
||||
return $executions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testCreateExecution
|
||||
* @param $function
|
||||
* @param $execution
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetExecution($function, $execution): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_EXECUTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'executionId' => $execution['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$execution = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($execution['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $execution['body']);
|
||||
$execution = $execution['body']['data']['functionsGetExecution'];
|
||||
$this->assertIsArray($execution);
|
||||
|
||||
return $execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @param $function
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testUpdateFunction($function): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_FUNCTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'name' => 'Test Function Updated',
|
||||
'execute' => [Role::any()->toString()],
|
||||
'vars' => [
|
||||
'name' => 'John Doe',
|
||||
'age' => 42,
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$function = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($function['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $function['body']);
|
||||
$function = $function['body']['data']['functionsUpdate'];
|
||||
$this->assertIsArray($function);
|
||||
|
||||
return $function;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testCreateDeployment
|
||||
* @param $function
|
||||
* @param $deployment
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteDeployment($function, $deployment): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_DEPLOYMENT);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
'deploymentId' => $deployment['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($response['body']);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFunction
|
||||
* @depends testDeleteDeployment
|
||||
* @param $function
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteFunction($function): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_FUNCTION);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'functionId' => $function['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($response['body']);
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
225
tests/e2e/Services/GraphQL/HealthTest.php
Normal file
225
tests/e2e/Services/GraphQL/HealthTest.php
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class HealthTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testGetHTTPHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_HTTP_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$httpHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($httpHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $httpHealth['body']);
|
||||
$httpHealth = $httpHealth['body']['data']['healthGet'];
|
||||
$this->assertIsArray($httpHealth);
|
||||
|
||||
return $httpHealth;
|
||||
}
|
||||
|
||||
public function testGetDBHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_DB_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$dbHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($dbHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $dbHealth['body']);
|
||||
$dbHealth = $dbHealth['body']['data']['healthGetDB'];
|
||||
$this->assertIsArray($dbHealth);
|
||||
|
||||
return $dbHealth;
|
||||
}
|
||||
|
||||
public function testGetCacheHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_CACHE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$cacheHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($cacheHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $cacheHealth['body']);
|
||||
$cacheHealth = $cacheHealth['body']['data']['healthGetCache'];
|
||||
$this->assertIsArray($cacheHealth);
|
||||
|
||||
return $cacheHealth;
|
||||
}
|
||||
|
||||
public function testGetTimeHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TIME_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$timeHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($timeHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $timeHealth['body']);
|
||||
$timeHealth = $timeHealth['body']['data']['healthGetTime'];
|
||||
$this->assertIsArray($timeHealth);
|
||||
|
||||
return $timeHealth;
|
||||
}
|
||||
|
||||
public function testGetWebhooksQueueHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_WEBHOOKS_QUEUE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$webhooksQueueHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($webhooksQueueHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $webhooksQueueHealth['body']);
|
||||
$webhooksQueueHealth = $webhooksQueueHealth['body']['data']['healthGetQueueWebhooks'];
|
||||
$this->assertIsArray($webhooksQueueHealth);
|
||||
|
||||
return $webhooksQueueHealth;
|
||||
}
|
||||
|
||||
public function testGetLogsQueueHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_LOGS_QUEUE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$logsQueueHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($logsQueueHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $logsQueueHealth['body']);
|
||||
$logsQueueHealth = $logsQueueHealth['body']['data']['healthGetQueueLogs'];
|
||||
$this->assertIsArray($logsQueueHealth);
|
||||
|
||||
return $logsQueueHealth;
|
||||
}
|
||||
|
||||
public function testGetCertificatesQueueHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_CERTIFICATES_QUEUE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$certificatesQueueHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($certificatesQueueHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $certificatesQueueHealth['body']);
|
||||
$certificatesQueueHealth = $certificatesQueueHealth['body']['data']['healthGetQueueCertificates'];
|
||||
$this->assertIsArray($certificatesQueueHealth);
|
||||
|
||||
return $certificatesQueueHealth;
|
||||
}
|
||||
|
||||
public function testGetFunctionsQueueHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FUNCTION_QUEUE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$functionsQueueHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($functionsQueueHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $functionsQueueHealth['body']);
|
||||
$functionsQueueHealth = $functionsQueueHealth['body']['data']['healthGetQueueFunctions'];
|
||||
$this->assertIsArray($functionsQueueHealth);
|
||||
|
||||
return $functionsQueueHealth;
|
||||
}
|
||||
|
||||
public function testGetLocalStorageHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_LOCAL_STORAGE_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$localStorageHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($localStorageHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $localStorageHealth['body']);
|
||||
$localStorageHealth = $localStorageHealth['body']['data']['healthGetStorageLocal'];
|
||||
$this->assertIsArray($localStorageHealth);
|
||||
|
||||
return $localStorageHealth;
|
||||
}
|
||||
|
||||
public function testGetAntiVirusHealth()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_ANITVIRUS_HEALTH);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$antiVirusHealth = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($antiVirusHealth['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $antiVirusHealth['body']);
|
||||
$antiVirusHealth = $antiVirusHealth['body']['data']['healthGetAntivirus'];
|
||||
$this->assertIsArray($antiVirusHealth);
|
||||
|
||||
return $antiVirusHealth;
|
||||
}
|
||||
}
|
||||
165
tests/e2e/Services/GraphQL/LocalizationTest.php
Normal file
165
tests/e2e/Services/GraphQL/LocalizationTest.php
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class LocalizationTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testGetLocale(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = \urlencode($this->getQuery(self::$GET_LOCALE));
|
||||
|
||||
$locale = $this->client->call(Client::METHOD_GET, '/graphql?query=' . $query, \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertIsArray($locale['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $locale['body']);
|
||||
$locale = $locale['body']['data']['localeGet'];
|
||||
$this->assertIsArray($locale);
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
public function testGetCountries(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_COUNTRIES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$countries = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($countries['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $countries['body']);
|
||||
$countries = $countries['body']['data']['localeListCountries'];
|
||||
$this->assertIsArray($countries);
|
||||
$this->assertGreaterThan(0, \count($countries));
|
||||
|
||||
return $countries;
|
||||
}
|
||||
|
||||
public function testGetCountriesEU(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_EU_COUNTRIES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$countries = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($countries['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $countries['body']);
|
||||
$countries = $countries['body']['data']['localeListCountriesEU'];
|
||||
$this->assertIsArray($countries);
|
||||
$this->assertGreaterThan(0, \count($countries));
|
||||
|
||||
return $countries;
|
||||
}
|
||||
|
||||
public function testGetCountriesPhones(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_COUNTRY_PHONE_CODES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$countries = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($countries['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $countries['body']);
|
||||
$countries = $countries['body']['data']['localeListCountriesPhones'];
|
||||
$this->assertIsArray($countries);
|
||||
$this->assertGreaterThan(0, \count($countries));
|
||||
|
||||
return $countries;
|
||||
}
|
||||
|
||||
public function testGetContinents(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_CONTINENTS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$continents = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($continents['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $continents['body']);
|
||||
$continents = $continents['body']['data']['localeListContinents'];
|
||||
$this->assertIsArray($continents);
|
||||
$this->assertGreaterThan(0, \count($continents));
|
||||
|
||||
return $continents;
|
||||
}
|
||||
|
||||
public function testGetCurrencies(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_CURRENCIES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$currencies = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($currencies['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $currencies['body']);
|
||||
$currencies = $currencies['body']['data']['localeListCurrencies'];
|
||||
$this->assertIsArray($currencies);
|
||||
$this->assertGreaterThan(0, \count($currencies));
|
||||
|
||||
return $currencies;
|
||||
}
|
||||
|
||||
public function testGetLanguages(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$LIST_LANGUAGES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$languages = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($languages['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $languages['body']);
|
||||
$languages = $languages['body']['data']['localeListLanguages'];
|
||||
$this->assertIsArray($languages);
|
||||
$this->assertGreaterThan(0, \count($languages));
|
||||
|
||||
return $languages;
|
||||
}
|
||||
}
|
||||
66
tests/e2e/Services/GraphQL/ScopeTest.php
Normal file
66
tests/e2e/Services/GraphQL/ScopeTest.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class ScopeTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testInvalidScope()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$apiKey = $this->getNewKey(['databases.read']);
|
||||
$query = $this->getQuery(self::$CREATE_DATABASE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
]
|
||||
];
|
||||
|
||||
$database = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apiKey,
|
||||
], $gqlPayload);
|
||||
|
||||
$message = "app.${projectId}@service.localhost (role: applications) missing scope (databases.write)";
|
||||
$this->assertArrayHasKey('errors', $database['body']);
|
||||
$this->assertEquals($message, $database['body']['errors'][0]['message']);
|
||||
}
|
||||
|
||||
public function testValidScope()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$apiKey = $this->getNewKey(['databases.read', 'databases.write']);
|
||||
$query = $this->getQuery(self::$CREATE_DATABASE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Actors',
|
||||
]
|
||||
];
|
||||
|
||||
$database = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $apiKey,
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($database['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $database['body']);
|
||||
$database = $database['body']['data']['databasesCreate'];
|
||||
$this->assertEquals('Actors', $database['name']);
|
||||
}
|
||||
}
|
||||
296
tests/e2e/Services/GraphQL/StorageClientTest.php
Normal file
296
tests/e2e/Services/GraphQL/StorageClientTest.php
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class StorageClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideClient;
|
||||
use Base;
|
||||
|
||||
public function testCreateBucket(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => 'actors',
|
||||
'name' => 'Actors',
|
||||
'fileSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $gqlPayload);
|
||||
|
||||
$this->assertIsArray($bucket['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $bucket['body']);
|
||||
$bucket = $bucket['body']['data']['storageCreateBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
*/
|
||||
public function testCreateFile($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_FILE);
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'fileId' => ID::unique(),
|
||||
'file' => null,
|
||||
'fileSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
]),
|
||||
'map' => \json_encode([
|
||||
'0' => ["variables.file"]
|
||||
]),
|
||||
'0' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
|
||||
return $file['body']['data']['storageCreateFile'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @param $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFiles($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILES);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$files = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($files['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $files['body']);
|
||||
$files = $files['body']['data']['storageListFiles'];
|
||||
$this->assertIsArray($files);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @depends testCreateFile
|
||||
* @param $bucket
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFile($bucket, $file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
|
||||
return $file['body']['data']['storageGetFile'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFilePreview($file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_PREVIEW);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
'width' => 100,
|
||||
'height' => 100,
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(46719, \strlen($file['body']));
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFileDownload($file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_DOWNLOAD);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFileView($file): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_VIEW);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testUpdateFile($file): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
$file = $file['body']['data']['storageUpdateFile'];
|
||||
$this->assertIsArray($file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteFile($file): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($file['body']);
|
||||
$this->assertEquals(204, $file['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
403
tests/e2e/Services/GraphQL/StorageServerTest.php
Normal file
403
tests/e2e/Services/GraphQL/StorageServerTest.php
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use CURLFile;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Permission;
|
||||
use Utopia\Database\Role;
|
||||
|
||||
class StorageServerTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testCreateBucket(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => 'actors',
|
||||
'name' => 'Actors',
|
||||
'fileSecurity' => false,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($bucket['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $bucket['body']);
|
||||
$bucket = $bucket['body']['data']['storageCreateBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
*/
|
||||
public function testCreateFile($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_FILE);
|
||||
$gqlPayload = [
|
||||
'operations' => \json_encode([
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'fileId' => ID::unique(),
|
||||
'file' => null,
|
||||
'fileSecurity' => true,
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
]),
|
||||
'map' => \json_encode([
|
||||
'file' => ["variables.file"]
|
||||
]),
|
||||
'file' => new CURLFile(realpath(__DIR__ . '/../../../resources/logo.png'), 'image/png', 'logo.png'),
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql/upload', \array_merge([
|
||||
'content-type' => 'multipart/form-data',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
return $file['body']['data']['storageCreateFile'];
|
||||
}
|
||||
|
||||
public function testGetBuckets(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_BUCKETS);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$buckets = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($buckets['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $buckets['body']);
|
||||
$buckets = $buckets['body']['data']['storageListBuckets'];
|
||||
$this->assertIsArray($buckets);
|
||||
|
||||
return $buckets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @param $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetBucket($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($bucket['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $bucket['body']);
|
||||
$bucket = $bucket['body']['data']['storageGetBucket'];
|
||||
$this->assertEquals('Actors', $bucket['name']);
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @param $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFiles($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILES);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$files = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($files['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $files['body']);
|
||||
$files = $files['body']['data']['storageListFiles'];
|
||||
$this->assertIsArray($files);
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @depends testCreateFile
|
||||
* @param $bucket
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFile($bucket, $file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
|
||||
return $file['body']['data']['storageGetFile'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFilePreview($file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_PREVIEW);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
'width' => 100,
|
||||
'height' => 100,
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(46719, \strlen($file['body']));
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFileDownload($file)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_DOWNLOAD);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testGetFileView($file): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_FILE_VIEW);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @param $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testUpdateBucket($bucket): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
'name' => 'Actors Updated',
|
||||
'fileSecurity' => false,
|
||||
]
|
||||
];
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($bucket['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $bucket['body']);
|
||||
$bucket = $bucket['body']['data']['storageUpdateBucket'];
|
||||
$this->assertEquals('Actors Updated', $bucket['name']);
|
||||
|
||||
return $bucket;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testUpdateFile($file): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsArray($file['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $file['body']);
|
||||
$file = $file['body']['data']['storageUpdateFile'];
|
||||
$this->assertIsArray($file);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateFile
|
||||
* @param $file
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteFile($file): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_FILE);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $file['bucketId'],
|
||||
'fileId' => $file['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$file = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($file['body']);
|
||||
$this->assertEquals(204, $file['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateBucket
|
||||
* @param $bucket
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testDeleteBucket($bucket): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_BUCKET);
|
||||
$gqlPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'bucketId' => $bucket['_id'],
|
||||
]
|
||||
];
|
||||
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertIsNotArray($bucket['body']);
|
||||
$this->assertEquals(204, $bucket['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
190
tests/e2e/Services/GraphQL/TeamsClientTest.php
Normal file
190
tests/e2e/Services/GraphQL/TeamsClientTest.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class TeamsClientTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use Base;
|
||||
use SideClient;
|
||||
|
||||
public function testCreateTeam(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Team Name',
|
||||
'roles' => ['admin', 'developer', 'guest'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsCreate'];
|
||||
$this->assertEquals('Team Name', $team['name']);
|
||||
|
||||
return $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testCreateTeamMembership($team): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'email' => 'user@appwrite.io',
|
||||
'roles' => ['developer'],
|
||||
'url' => 'http://localhost/membership',
|
||||
],
|
||||
];
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/graphql', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
], $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($membership['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $membership['body']);
|
||||
$membership = $membership['body']['data']['teamsCreateMembership'];
|
||||
$this->assertEquals($team['_id'], $membership['teamId']);
|
||||
$this->assertEquals(['developer'], $membership['roles']);
|
||||
|
||||
return $membership;
|
||||
}
|
||||
|
||||
public function testGetTeams()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAMS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$teams = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($teams['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $teams['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testGetTeam($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsGet'];
|
||||
$this->assertIsArray($team);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testGetTeamMemberships($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM_MEMBERSHIPS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$memberships = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($memberships['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $memberships['body']);
|
||||
$this->assertIsArray($memberships['body']['data']['teamsListMemberships']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
* @depends testCreateTeamMembership
|
||||
*/
|
||||
public function testGetTeamMembership($team, $membership)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'membershipId' => $membership['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($membership['body']['data']['teamsGetMembership']);
|
||||
$this->assertArrayNotHasKey('errors', $membership['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
* @depends testCreateTeamMembership
|
||||
*/
|
||||
public function testDeleteTeamMembership($team, $membership)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'membershipId' => $membership['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($team['body']);
|
||||
$this->assertEquals(204, $team['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
265
tests/e2e/Services/GraphQL/TeamsServerTest.php
Normal file
265
tests/e2e/Services/GraphQL/TeamsServerTest.php
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
|
||||
class TeamsServerTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use Base;
|
||||
use SideServer;
|
||||
|
||||
public function testCreateTeam(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => ID::unique(),
|
||||
'name' => 'Team Name',
|
||||
'roles' => ['admin', 'developer', 'guest'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsCreate'];
|
||||
$this->assertEquals('Team Name', $team['name']);
|
||||
|
||||
return $team;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testCreateTeamMembership($team): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'email' => 'user@appwrite.io',
|
||||
'roles' => ['developer'],
|
||||
'url' => 'http://localhost/membership',
|
||||
],
|
||||
];
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($membership['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $membership['body']);
|
||||
$membership = $membership['body']['data']['teamsCreateMembership'];
|
||||
$this->assertEquals($team['_id'], $membership['teamId']);
|
||||
$this->assertEquals(['developer'], $membership['roles']);
|
||||
|
||||
return $membership;
|
||||
}
|
||||
|
||||
public function testGetTeams()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAMS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
];
|
||||
|
||||
$teams = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($teams['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $teams['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testGetTeam($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsGet'];
|
||||
$this->assertIsArray($team);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testGetTeamMemberships($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM_MEMBERSHIPS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$memberships = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($memberships['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $memberships['body']);
|
||||
$this->assertIsArray($memberships['body']['data']['teamsListMemberships']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
* @depends testCreateTeamMembership
|
||||
*/
|
||||
public function testGetTeamMembership($team, $membership)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'membershipId' => $membership['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($membership['body']['data']['teamsGetMembership']);
|
||||
$this->assertArrayNotHasKey('errors', $membership['body']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
*/
|
||||
public function testUpdateTeam($team)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'name' => 'New Name',
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($team['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $team['body']);
|
||||
$team = $team['body']['data']['teamsUpdate'];
|
||||
$this->assertEquals('New Name', $team['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
* @depends testCreateTeamMembership
|
||||
*/
|
||||
public function testUpdateTeamMembershipRoles($team, $membership)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_TEAM_MEMBERSHIP_ROLES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'membershipId' => $membership['_id'],
|
||||
'roles' => ['developer', 'admin'],
|
||||
],
|
||||
];
|
||||
|
||||
$membership = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($membership['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $membership['body']);
|
||||
$membership = $membership['body']['data']['teamsUpdateMembershipRoles'];
|
||||
$this->assertEquals(['developer', 'admin'], $membership['roles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testCreateTeam
|
||||
* @depends testCreateTeamMembership
|
||||
*/
|
||||
public function testDeleteTeamMembership($team, $membership)
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_TEAM_MEMBERSHIP);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
'membershipId' => $membership['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($team['body']);
|
||||
$this->assertEquals(204, $team['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testDeleteTeam()
|
||||
{
|
||||
$team = $this->testCreateTeam();
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_TEAM);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'teamId' => $team['_id'],
|
||||
],
|
||||
];
|
||||
|
||||
$team = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($team['body']);
|
||||
$this->assertEquals(204, $team['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
429
tests/e2e/Services/GraphQL/UsersTest.php
Normal file
429
tests/e2e/Services/GraphQL/UsersTest.php
Normal file
|
|
@ -0,0 +1,429 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\GraphQL;
|
||||
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
use Utopia\Database\ID;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class UsersTest extends Scope
|
||||
{
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
use Base;
|
||||
|
||||
public function testCreateUser(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$CREATE_USER);
|
||||
$email = 'users.service@example.com';
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Project User',
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
|
||||
$user = $user['body']['data']['usersCreate'];
|
||||
$this->assertEquals('Project User', $user['name']);
|
||||
$this->assertEquals($email, $user['email']);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function testGetUsers()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USERS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'queries' => [
|
||||
'limit(100)',
|
||||
'offset(0)',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$users = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($users['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $users['body']);
|
||||
$this->assertIsArray($users['body']['data']['usersList']);
|
||||
$this->assertGreaterThan(0, \count($users['body']['data']['usersList']));
|
||||
}
|
||||
|
||||
public function testGetUser()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersGet']);
|
||||
$this->assertEquals($this->getUser()['$id'], $user['body']['data']['usersGet']['_id']);
|
||||
}
|
||||
|
||||
public function testGetUserPreferences()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_PREFERENCES);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersGetPrefs']);
|
||||
}
|
||||
|
||||
public function testGetUserSessions()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_SESSIONS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersListSessions']);
|
||||
}
|
||||
|
||||
public function testGetUserMemberships()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_MEMBERSHIPS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersListMemberships']);
|
||||
}
|
||||
|
||||
public function testGetUserLogs()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$GET_USER_LOGS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersListLogs']);
|
||||
}
|
||||
|
||||
public function testUpdateUserStatus()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_STATUS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'status' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdateStatus']);
|
||||
$this->assertEquals($this->getUser()['$id'], $user['body']['data']['usersUpdateStatus']['_id']);
|
||||
}
|
||||
|
||||
public function testUpdateUserEmailVerification()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_EMAIL_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'emailVerification' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdateEmailVerification']);
|
||||
}
|
||||
|
||||
public function testUpdateUserPhoneVerification()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_PHONE_VERIFICATION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'phoneVerification' => true,
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdatePhoneVerification']);
|
||||
$this->assertEquals($this->getUser()['$id'], $user['body']['data']['usersUpdatePhoneVerification']['_id']);
|
||||
}
|
||||
|
||||
public function testUpdateUserName()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_NAME);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'name' => 'Updated Name',
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdateName']);
|
||||
$this->assertEquals('Updated Name', $user['body']['data']['usersUpdateName']['name']);
|
||||
}
|
||||
|
||||
public function testUpdateUserEmail()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_EMAIL);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'email' => 'newemail@appwrite.io'
|
||||
],
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdateEmail']);
|
||||
$this->assertEquals('newemail@appwrite.io', $user['body']['data']['usersUpdateEmail']['email']);
|
||||
}
|
||||
|
||||
public function testUpdateUserPassword()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_PASSWORD);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'password' => 'newpassword'
|
||||
],
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdatePassword']);
|
||||
}
|
||||
|
||||
public function testUpdateUserPhone()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_PHONE);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'number' => '+123456789'
|
||||
],
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdatePhone']);
|
||||
$this->assertEquals('+123456789', $user['body']['data']['usersUpdatePhone']['phone']);
|
||||
}
|
||||
|
||||
public function testUpdateUserPrefs()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$UPDATE_USER_PREFS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'prefs' => [
|
||||
'key' => 'value'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsArray($user['body']['data']);
|
||||
$this->assertArrayNotHasKey('errors', $user['body']);
|
||||
$this->assertIsArray($user['body']['data']['usersUpdatePrefs']);
|
||||
$this->assertEquals('{"key":"value"}', $user['body']['data']['usersUpdatePrefs']['data']);
|
||||
}
|
||||
|
||||
public function testDeleteUserSessions()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_USER_SESSIONS);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($user['body']);
|
||||
$this->assertEquals(204, $user['headers']['status-code']);
|
||||
|
||||
unset(self::$user[$this->getProject()['$id']]);
|
||||
$this->getUser();
|
||||
}
|
||||
|
||||
public function testDeleteUserSession()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_USER_SESSION);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
'sessionId' => $this->getUser()['sessionId'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($user['body']);
|
||||
$this->assertEquals(204, $user['headers']['status-code']);
|
||||
|
||||
unset(self::$user[$this->getProject()['$id']]);
|
||||
$this->getUser();
|
||||
}
|
||||
|
||||
public function testDeleteUser()
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$query = $this->getQuery(self::$DELETE_USER);
|
||||
$graphQLPayload = [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'userId' => $this->getUser()['$id'],
|
||||
]
|
||||
];
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/graphql', \array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()), $graphQLPayload);
|
||||
|
||||
$this->assertIsNotArray($user['body']);
|
||||
$this->assertEquals(204, $user['headers']['status-code']);
|
||||
}
|
||||
}
|
||||
|
|
@ -874,6 +874,7 @@ trait UsersBase
|
|||
/**
|
||||
* @depends testGetUser
|
||||
*/
|
||||
#[Retry(count: 1)]
|
||||
public function testUpdateAndGetUserPrefs(array $data): array
|
||||
{
|
||||
/**
|
||||
|
|
|
|||
29
tests/unit/GraphQL/BuilderTest.php
Normal file
29
tests/unit/GraphQL/BuilderTest.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\GraphQL;
|
||||
|
||||
use Appwrite\GraphQL\Types\Mapper;
|
||||
use Appwrite\Utopia\Response;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
|
||||
class BuilderTest extends TestCase
|
||||
{
|
||||
protected ?Response $response = null;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->response = new Response(new SwooleResponse());
|
||||
Mapper::init($this->response->getModels());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testCreateTypeMapping()
|
||||
{
|
||||
$model = $this->response->getModel(Response::MODEL_COLLECTION);
|
||||
$type = Mapper::model(\ucfirst($model->getType()));
|
||||
$this->assertEquals('Collection', $type->name);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue