diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..5cdf5a9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,113 @@ +language: php +sudo: false + +php: + - 7.1 + - 7 + - 5.6 + - 5.5 + +env: + global: + - DRUPAL_BUILD_DIR=$TRAVIS_BUILD_DIR/../drupal + - SIMPLETEST_DB=mysql://root:@127.0.0.1/graphql + - TRAVIS=true + matrix: + - DRUPAL_CORE=8.3.x + - DRUPAL_CORE=8.4.x + - DRUPAL_CORE=8.5.x + +matrix: + # Don't wait for the allowed failures to build. + fast_finish: true + include: + - php: 7.1 + env: + - DRUPAL_CORE=8.5.x + # Only run code coverage on the latest php and drupal versions. + - WITH_PHPDBG_COVERAGE=true + allow_failures: + # Allow the code coverage report to fail. + - php: 7.1 + env: + - DRUPAL_CORE=8.5.x + # Only run code coverage on the latest php and drupal versions. + - WITH_PHPDBG_COVERAGE=true + +mysql: + database: graphql + username: root + encoding: utf8 + +# Cache composer downloads. +cache: + directories: + - $HOME/.composer + +before_install: + # Disable xdebug. + - phpenv config-rm xdebug.ini + + # Determine the php settings file location. + - if [[ $TRAVIS_PHP_VERSION = hhvm* ]]; + then export PHPINI=/etc/hhvm/php.ini; + else export PHPINI=$HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; + fi + + # PHP Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated + # and will be removed in a future version. To avoid this warning set + # 'always_populate_raw_post_data' to '-1' in php.ini and use the php://input + # stream instead. + - if [[ "$TRAVIS_PHP_VERSION" == "5.6" ]]; + then echo always_populate_raw_post_data = -1 >> $PHPINI; + fi; + + # Disable the default memory limit. + - echo memory_limit = -1 >> $PHPINI + + # Update composer. + - composer self-update + +install: + # Create the database. + - mysql -e 'create database graphql' + + # Download Drupal 8 core from the Github mirror because it is faster. + - git clone --branch $DRUPAL_CORE --depth 1 https://github.com/drupal/drupal.git $DRUPAL_BUILD_DIR + + # Download graphql module from the Github mirror because it is faster. + - git clone --depth 1 https://github.com/drupal-graphql/graphql.git $DRUPAL_BUILD_DIR/modules/contrib/graphql + + # Reference the module in the build site. + - ln -s $TRAVIS_BUILD_DIR $DRUPAL_BUILD_DIR/modules/graphql-examples + + # Copy the customized phpunit configuration file to the core directory so + # the relative paths are correct. + - cp $DRUPAL_BUILD_DIR/modules/contrib/graphql/phpunit.xml.dist $DRUPAL_BUILD_DIR/core/phpunit.xml + + # When running with phpdbg we need to replace all code occurrences that check + # for 'cli' with 'phpdbg'. Some files might be write protected, hence the + # fallback. + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then grep -rl 'cli' $DRUPAL_BUILD_DIR/core $DRUPAL_BUILD_DIR/modules | xargs sed -i "s/'cli'/'phpdbg'/g" || true; + fi + + # Bring in the module dependencies without requiring a merge plugin. The + # require also triggers a full 'composer install'. + - composer --working-dir=$DRUPAL_BUILD_DIR require webonyx/graphql-php:^0.11.5 + +script: + # Run the unit tests using phpdbg if the environment variable is 'true'. + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then phpdbg -qrr $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml --coverage-clover $TRAVIS_BUILD_DIR/coverage.xml $TRAVIS_BUILD_DIR; + fi + + # Run the unit tests with standard php otherwise. + - if [[ "$WITH_PHPDBG_COVERAGE" != "true" ]]; + then $DRUPAL_BUILD_DIR/vendor/bin/phpunit --configuration $DRUPAL_BUILD_DIR/core/phpunit.xml $TRAVIS_BUILD_DIR; + fi + +after_success: + - if [[ "$WITH_PHPDBG_COVERAGE" == "true" ]]; + then bash <(curl -s https://codecov.io/bash); + fi \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..000a269 --- /dev/null +++ b/composer.json @@ -0,0 +1,13 @@ +{ + "name": "drupal/graphql_examples", + "type": "drupal-module", + "description": "A GraphQL Article and File Mutation Example", + "keywords": ["Drupal"], + "license": "GPL-2.0+", + "minimum-stability": "dev", + "support": { + "issues": "https://github.com/drupal-graphql/graphql-examples", + "source": "https://github.com/drupal-graphql/graphql-examples" + }, + "require": { } +} diff --git a/config/optional/core.entity_form_display.node.article.default.yml b/config/optional/core.entity_form_display.node.article.default.yml new file mode 100644 index 0000000..0198515 --- /dev/null +++ b/config/optional/core.entity_form_display.node.article.default.yml @@ -0,0 +1,112 @@ +langcode: en +status: true +dependencies: + config: + - entity_browser.browser.media_entity_browser + - field.field.node.article.body + - field.field.node.article.field_image + - field.field.node.article.field_media_image + - field.field.node.article.field_tags + - image.style.thumbnail + - node.type.article + module: + - entity_browser + - image + - path + - text +id: node.article.default +targetEntityType: node +bundle: article +mode: default +content: + body: + type: text_textarea_with_summary + weight: 1 + region: content + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + created: + type: datetime_timestamp + weight: 6 + region: content + settings: { } + third_party_settings: { } + field_image: + type: image_image + weight: 3 + region: content + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + field_media_image: + weight: 4 + settings: + entity_browser: media_entity_browser + field_widget_display: rendered_entity + field_widget_edit: true + field_widget_remove: true + open: true + selection_mode: selection_append + field_widget_display_settings: + view_mode: default + third_party_settings: { } + type: entity_browser_entity_reference + region: content + field_tags: + type: entity_reference_autocomplete_tags + weight: 2 + region: content + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } + path: + type: path + weight: 9 + region: content + settings: { } + third_party_settings: { } + promote: + type: boolean_checkbox + settings: + display_label: true + weight: 7 + region: content + third_party_settings: { } + status: + type: boolean_checkbox + settings: + display_label: true + weight: 10 + region: content + third_party_settings: { } + sticky: + type: boolean_checkbox + settings: + display_label: true + weight: 8 + region: content + third_party_settings: { } + title: + type: string_textfield + weight: 0 + region: content + settings: + size: 60 + placeholder: '' + third_party_settings: { } + uid: + type: entity_reference_autocomplete + weight: 5 + region: content + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/config/optional/entity_browser.browser.media_entity_browser.yml b/config/optional/entity_browser.browser.media_entity_browser.yml new file mode 100644 index 0000000..847ab81 --- /dev/null +++ b/config/optional/entity_browser.browser.media_entity_browser.yml @@ -0,0 +1,43 @@ +langcode: en +status: true +dependencies: + config: + - views.view.media_entity_browser + module: + - views +_core: + default_config_hash: UoGZLPH_DEy28wDjCYqiBKYsKoHlf6l6deygJjqdgPc +name: media_entity_browser +label: 'Media Entity Browser' +display: modal +display_configuration: + width: '' + height: '' + link_text: Select + auto_open: false +selection_display: no_display +selection_display_configuration: { } +widget_selector: tabs +widget_selector_configuration: { } +widgets: + 6586703a-6976-4124-8a49-cbb07ceaa3b1: + settings: + view: media_entity_browser + view_display: entity_browser_1 + submit_text: Select + auto_select: false + uuid: 6586703a-6976-4124-8a49-cbb07ceaa3b1 + weight: 1 + label: view + id: view + 18d9da69-b4e6-4be3-a8bc-bb3fa8574f15: + settings: + extensions: 'jpg jpeg png gif' + media_type: image + upload_location: 'public://' + multiple: '1' + submit_text: 'Select files' + uuid: 18d9da69-b4e6-4be3-a8bc-bb3fa8574f15 + weight: 2 + label: Upload + id: media_image_upload diff --git a/config/optional/field.field.node.article.body.yml b/config/optional/field.field.node.article.body.yml new file mode 100644 index 0000000..8f3681d --- /dev/null +++ b/config/optional/field.field.node.article.body.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.body + - node.type.article + module: + - text +id: node.article.body +field_name: body +entity_type: node +bundle: article +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: true +field_type: text_with_summary diff --git a/config/optional/field.field.node.article.field_image.yml b/config/optional/field.field.node.article.field_image.yml new file mode 100644 index 0000000..b4b1c14 --- /dev/null +++ b/config/optional/field.field.node.article.field_image.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_image + - node.type.article + module: + - image +id: node.article.field_image +field_name: field_image +entity_type: node +bundle: article +label: Image +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: true + title_field: false + alt_field_required: true + title_field_required: false + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/config/optional/field.field.node.article.field_media_image.yml b/config/optional/field.field.node.article.field_media_image.yml new file mode 100644 index 0000000..f510a1b --- /dev/null +++ b/config/optional/field.field.node.article.field_media_image.yml @@ -0,0 +1,27 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_media_image + - media.type.image + - node.type.article +id: node.article.field_media_image +field_name: field_media_image +entity_type: node +bundle: article +label: 'Media Image' +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + handler: 'default:media' + handler_settings: + target_bundles: + image: image + sort: + field: _none + auto_create: false + auto_create_bundle: '' +field_type: entity_reference diff --git a/config/optional/field.field.node.article.field_tags.yml b/config/optional/field.field.node.article.field_tags.yml new file mode 100644 index 0000000..1b9c4cc --- /dev/null +++ b/config/optional/field.field.node.article.field_tags.yml @@ -0,0 +1,26 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_tags + - node.type.article + - taxonomy.vocabulary.tags +id: node.article.field_tags +field_name: field_tags +entity_type: node +bundle: article +label: Tags +description: 'Enter a comma-separated list. For example: Amsterdam, Mexico City, "Cleveland, Ohio"' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + handler: 'default:taxonomy_term' + handler_settings: + target_bundles: + tags: tags + sort: + field: _none + auto_create: true +field_type: entity_reference diff --git a/config/optional/field.storage.node.body.yml b/config/optional/field.storage.node.body.yml new file mode 100644 index 0000000..73edd16 --- /dev/null +++ b/config/optional/field.storage.node.body.yml @@ -0,0 +1,18 @@ +langcode: en +status: true +dependencies: + module: + - node + - text +id: node.body +field_name: body +entity_type: node +type: text_with_summary +settings: { } +module: text +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: true +custom_storage: false diff --git a/config/optional/field.storage.node.field_image.yml b/config/optional/field.storage.node.field_image.yml new file mode 100644 index 0000000..e4da708 --- /dev/null +++ b/config/optional/field.storage.node.field_image.yml @@ -0,0 +1,31 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - node +id: node.field_image +field_name: field_image +entity_type: node +type: image +settings: + uri_scheme: public + default_image: + uuid: null + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: + target_id: + - target_id +persist_with_no_fields: false +custom_storage: false diff --git a/config/optional/field.storage.node.field_media_image.yml b/config/optional/field.storage.node.field_media_image.yml new file mode 100644 index 0000000..11348eb --- /dev/null +++ b/config/optional/field.storage.node.field_media_image.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - media + - node +id: node.field_media_image +field_name: field_media_image +entity_type: node +type: entity_reference +settings: + target_type: media +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/optional/field.storage.node.field_tags.yml b/config/optional/field.storage.node.field_tags.yml new file mode 100644 index 0000000..73f821f --- /dev/null +++ b/config/optional/field.storage.node.field_tags.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - node + - taxonomy +id: node.field_tags +field_name: field_tags +entity_type: node +type: entity_reference +settings: + target_type: taxonomy_term +module: core +locked: false +cardinality: -1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/config/optional/image.style.thumbnail.yml b/config/optional/image.style.thumbnail.yml new file mode 100644 index 0000000..b275920 --- /dev/null +++ b/config/optional/image.style.thumbnail.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: { } +name: thumbnail +label: 'Thumbnail (100×100)' +effects: + id: image_scale + weight: 0 + data: + width: 100 + height: 100 + upscale: false diff --git a/config/optional/media.type.image.yml b/config/optional/media.type.image.yml new file mode 100644 index 0000000..252de4c --- /dev/null +++ b/config/optional/media.type.image.yml @@ -0,0 +1,12 @@ +langcode: en +status: true +dependencies: { } +id: image +label: Image +description: '' +source: image +queue_thumbnail_downloads: false +new_revision: false +source_configuration: + source_field: field_media_image +field_map: { } diff --git a/config/optional/node.type.article.yml b/config/optional/node.type.article.yml new file mode 100644 index 0000000..1fd439c --- /dev/null +++ b/config/optional/node.type.article.yml @@ -0,0 +1,10 @@ +langcode: en +status: true +dependencies: { } +name: Article +type: article +description: 'Use articles for time-sensitive content like news, press releases or blog posts.' +help: '' +new_revision: true +preview_mode: 1 +display_submitted: true diff --git a/config/optional/taxonomy.vocabulary.tags.yml b/config/optional/taxonomy.vocabulary.tags.yml new file mode 100644 index 0000000..8fac8f5 --- /dev/null +++ b/config/optional/taxonomy.vocabulary.tags.yml @@ -0,0 +1,8 @@ +langcode: en +status: true +dependencies: { } +name: Tags +vid: tags +description: 'Use tags to group articles on similar topics into categories.' +hierarchy: 0 +weight: 0 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..9976f2a --- /dev/null +++ b/readme.md @@ -0,0 +1,174 @@ +# Drupal GraphQL Mutation Example + + + +CREATE ARTICLE MUTATION +```$xslt +mutation { + createArticle(input:{title: "Hey", body:"Ho"}){ + errors, + violations{ + message, + path, + code + }, + entity { + entityUuid + ...on NodeArticle { + nid + title, + body { + value + format + processed + summary + summaryProcessed + } + } + } + } +} + +``` + +RESULT + +```$xslt +{ + "data": { + "createArticle": { + "errors": [], + "violations": [], + "entity": { + "entityUuid": "f0628b9d-bb9b-452a-aeea-bfdc2000660e", + "nid": 21, + "title": "Hey", + "body": { + "value": "Ho", + "format": "null", + "processed": "

Ho

\n", + "summary": "null", + "summaryProcessed": "" + } + } + } + } +} +``` + + +--- + + +UPDATE MUTATION + +```$xslt +mutation { + updateArticle(id:"21", input:{title: "Heyo", body:"Let's go"}){ + errors, + violations{ + message, + path, + code + }, + entity { + entityUuid + ...on NodeArticle { + nid + title, + body { + value + format + processed + summary + summaryProcessed + } + } + } + } +} +``` + + +RESULT +```$xslt +{ + "data": { + "updateArticle": { + "errors": [], + "violations": [], + "entity": { + "entityUuid": "f0628b9d-bb9b-452a-aeea-bfdc2000660e", + "nid": 21, + "title": "Heyo", + "body": { + "value": "Let's go", + "format": "null", + "processed": "

Let's go

\n", + "summary": "null", + "summaryProcessed": "" + } + } + } + } +} +``` + + +--- + + +DELETE MUTATION +```$xslt +mutation { + deleteArticle(id:21){ + errors, + violations{ + message, + path, + code + }, + entity { + entityUuid + ...on NodeArticle { + nid + title, + body { + value + format + processed + summary + summaryProcessed + } + } + } + } +} + +``` + + +RESULT + +```$xslt +{ + "data": { + "deleteArticle": { + "errors": [], + "violations": [], + "entity": { + "entityUuid": "f0628b9d-bb9b-452a-aeea-bfdc2000660e", + "nid": 21, + "title": "Heyo", + "body": { + "value": "Let's go", + "format": "null", + "processed": "

Let's go

\n", + "summary": "null", + "summaryProcessed": "" + } + } + } + } +} +``` \ No newline at end of file diff --git a/src/Plugin/GraphQL/InputTypes/ArticleInput.php b/src/Plugin/GraphQL/InputTypes/ArticleInput.php old mode 100644 new mode 100755 diff --git a/src/Plugin/GraphQL/Mutations/CreateArticle.php b/src/Plugin/GraphQL/Mutations/CreateArticle.php index bf3c76d..e94402a 100644 --- a/src/Plugin/GraphQL/Mutations/CreateArticle.php +++ b/src/Plugin/GraphQL/Mutations/CreateArticle.php @@ -2,9 +2,9 @@ namespace Drupal\graphql_examples\Plugin\GraphQL\Mutations; -use Drupal\graphql\GraphQL\Type\InputObjectType; use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\CreateEntityBase; -use Youshido\GraphQL\Execution\ResolveInfo; +use GraphQL\Type\Definition\ResolveInfo; +use Drupal\graphql\GraphQL\Execution\ResolveContext; /** * Simple mutation for creating a new article node. @@ -26,7 +26,7 @@ class CreateArticle extends CreateEntityBase { /** * {@inheritdoc} */ - protected function extractEntityInput(array $args, ResolveInfo $info) { + protected function extractEntityInput($value, array $args, ResolveContext $context, ResolveInfo $info) { return [ 'title' => $args['input']['title'], 'body' => $args['input']['body'], diff --git a/src/Plugin/GraphQL/Mutations/DeleteArticle.php b/src/Plugin/GraphQL/Mutations/DeleteArticle.php index 3634a1a..8a138d5 100644 --- a/src/Plugin/GraphQL/Mutations/DeleteArticle.php +++ b/src/Plugin/GraphQL/Mutations/DeleteArticle.php @@ -4,8 +4,9 @@ use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\DeleteEntityBase; + /** - * Simple mutation for deleting an article node. + * A Simple ArticleNode mutation. * * @GraphQLMutation( * id = "delete_article", diff --git a/src/Plugin/GraphQL/Mutations/FileUpload.php b/src/Plugin/GraphQL/Mutations/FileUpload.php index 938de59..93ebfa2 100644 --- a/src/Plugin/GraphQL/Mutations/FileUpload.php +++ b/src/Plugin/GraphQL/Mutations/FileUpload.php @@ -12,7 +12,8 @@ use Drupal\graphql_core\GraphQL\EntityCrudOutputWrapper; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface; -use Youshido\GraphQL\Execution\ResolveInfo; +use GraphQL\Type\Definition\ResolveInfo; +use Drupal\graphql\GraphQL\Execution\ResolveContext; /** * TODO: Add the whole range of file upload validations from file_save_upload(). @@ -97,7 +98,7 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function resolve($value, array $args, ResolveInfo $info) { + public function resolve($value, array $args, ResolveContext $context, ResolveInfo $info) { /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ $file = $args['file']; diff --git a/src/Plugin/GraphQL/Mutations/UpdateArticle.php b/src/Plugin/GraphQL/Mutations/UpdateArticle.php index 520e4a1..004d301 100644 --- a/src/Plugin/GraphQL/Mutations/UpdateArticle.php +++ b/src/Plugin/GraphQL/Mutations/UpdateArticle.php @@ -2,9 +2,9 @@ namespace Drupal\graphql_examples\Plugin\GraphQL\Mutations; -use Drupal\graphql\GraphQL\Type\InputObjectType; use Drupal\graphql_core\Plugin\GraphQL\Mutations\Entity\UpdateEntityBase; -use Youshido\GraphQL\Execution\ResolveInfo; +use GraphQL\Type\Definition\ResolveInfo; +use Drupal\graphql\GraphQL\Execution\ResolveContext; /** * Simple mutation for updating an existing article node. @@ -27,10 +27,10 @@ class UpdateArticle extends UpdateEntityBase { /** * {@inheritdoc} */ - protected function extractEntityInput(array $args, ResolveInfo $info) { + protected function extractEntityInput($value, array $args, ResolveContext $context, ResolveInfo $info) { return array_filter([ 'title' => $args['input']['title'], - 'body' => $args['input']['body'], + 'body' => key_exists('body', $args['input']) ? $args['input']['body'] : '', ]); } diff --git a/tests/queries/createArticle.gql b/tests/queries/createArticle.gql new file mode 100644 index 0000000..0d20183 --- /dev/null +++ b/tests/queries/createArticle.gql @@ -0,0 +1,18 @@ +mutation($input: ArticleInput){ + createArticle(input: $input){ + errors, + violations{ + message, + path, + code + }, + entity { + ...on NodeArticle { + title, + body { + value + } + } + } + } +} \ No newline at end of file diff --git a/tests/queries/deleteArticle.gql b/tests/queries/deleteArticle.gql new file mode 100644 index 0000000..dd8b41d --- /dev/null +++ b/tests/queries/deleteArticle.gql @@ -0,0 +1,18 @@ +mutation($id: String){ + deleteArticle(id: $id){ + errors, + violations{ + message, + path, + code + }, + entity { + ...on NodeArticle { + title, + body { + value + } + } + } + } +} \ No newline at end of file diff --git a/tests/queries/updateArticle.gql b/tests/queries/updateArticle.gql new file mode 100644 index 0000000..476f962 --- /dev/null +++ b/tests/queries/updateArticle.gql @@ -0,0 +1,18 @@ +mutation($id: String, $input: ArticleInput){ + updateArticle(id:$id, input:$input){ + errors, + violations{ + message, + path, + code + }, + entity { + ...on NodeArticle { + title, + body { + value + } + } + } + } +} \ No newline at end of file diff --git a/tests/src/Kernel/Extension/ArticleMutationTest.php b/tests/src/Kernel/Extension/ArticleMutationTest.php new file mode 100644 index 0000000..8bfdc8c --- /dev/null +++ b/tests/src/Kernel/Extension/ArticleMutationTest.php @@ -0,0 +1,190 @@ +installConfig(['file', 'image', 'taxonomy', 'graphql_examples']); + $this->installEntitySchema('taxonomy_vocabulary'); + $this->installEntitySchema('taxonomy_term'); + + } + + /** + * {@inheritdoc} + * + * Add the 'access content' permission to the mocked account. + */ + protected function userPermissions() { + $perms = parent::userPermissions(); + $perms[] = 'access content'; + $perms[] = 'create article content'; + $perms[] = 'delete own article content'; + $perms[] = 'edit own article content'; + $perms[] = 'execute graphql requests'; + return $perms; + } + + public function createTestArticle() { + return $this->createNode([ + 'title' => 'Hey', + 'status' => 1, + 'type' => 'article', + 'body' => [ + 'value' => 'Ho', + ], + ]); + } + + /** + * Test if the article is created properly. + */ + public function testCreateArticleMutation() { + $query = $this->getQueryFromFile('createArticle.gql'); + $variables = [ + 'input' => [ + 'title' => 'Hey', + 'body' => "Ho" + ] + ]; + $expected = [ + 'createArticle' => [ + 'errors' => [], + 'violations' => [], + 'entity' => [ + 'title' => 'Hey', + 'body' => [ + 'value' => 'Ho' + ] + ] + ], + ]; + $this->assertResults($query, $variables, $expected, $this->defaultMutationCacheMetaData()); + } + + /** + * Test if the article is NOT created properly. + */ + public function testCreateArticleFailureMutation() { + $query = $this->getQueryFromFile('createArticle.gql'); + $variables = [ + 'input' => [ + 'title' => 'Hey', + 'some-non-existent-field' => "Ho" + ] + ]; + $expected = [ + "Variable \"\$input\" got invalid value {\"title\":\"Hey\",\"some-non-existent-field\":\"Ho\"}. +In field \"some-non-existent-field\": Unknown field." + ]; + $this->assertErrors($query, $variables, $expected, $this->defaultMutationCacheMetaData()); + } + + + /** + * Test if the article is updated properly. + */ + public function testUpdateArticleMutation() { + + // SETUP + $node = $this->createTestArticle(); + + $query = $this->getQueryFromFile('updateArticle.gql'); + $variables = [ + 'id' => $node->id(), + 'input' => [ + 'title' => 'Heyo', + 'body' => "Let's go", + ] + ]; + $expected = [ + 'updateArticle' => [ + 'errors' => [], + 'violations' => [], + 'entity' => [ + 'title' => 'Heyo', + 'body' => [ + 'value' => "Let's go" + ] + ] + ], + ]; + $this->assertResults($query, $variables, $expected, $this->defaultMutationCacheMetaData()); + } + + /** + * Test if the article is deleted properly. + */ + public function testDeleteArticleMutation() { + + // SETUP + $node = $this->createTestArticle(); + + $query = $this->getQueryFromFile('deleteArticle.gql'); + $variables = [ + 'id' => $node->id(), + ]; + $expected = [ + 'deleteArticle' => [ + 'errors' => [], + 'violations' => [], + 'entity' => [ + 'title' => 'Hey', + 'body' => [ + 'value' => 'Ho' + ] + ] + ], + ]; + $this->assertResults($query, $variables, $expected, $this->defaultMutationCacheMetaData()); + } + + /** + * Test if the article is deleted properly. + */ + public function testDeleteNonExistentArticleMutation() { + + // SETUP + $node = $this->createTestArticle(); + + $query = $this->getQueryFromFile('deleteArticle.gql'); + $variables = [ + 'id' => 999, + ]; + // Note, the newline in the following string is required. + $expected = ["Variable \"\$id\" got invalid value 999. +Expected type \"String\", found 999."]; + $this->assertErrors($query, $variables, $expected, $this->defaultMutationCacheMetaData()); + } + + +} diff --git a/tests/src/Kernel/Framework/UploadMutationTest.php b/tests/src/Kernel/Framework/UploadMutationTest.php new file mode 100644 index 0000000..4b99afa --- /dev/null +++ b/tests/src/Kernel/Framework/UploadMutationTest.php @@ -0,0 +1,60 @@ +mockMutation('store', [ + 'name' => 'store', + 'arguments' => ['file' => 'Upload!'], + 'type' => 'String', + ], function ($value, $args) { + /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ + $file = $args['file']; + return $file->getClientOriginalName(); + }); + + // Create a post request with file contents. + $uploadRequest = Request::create('/graphql', 'POST', [ + 'query' => 'mutation($upload: Upload!) { store(file: $upload) }', + // The variable has to be declared null. + 'variables' => ['upload' => NULL], + // Then map the file upload name to the variable. + 'map' => [ + 'test' => ['variables.upload'], + ], + ], [], [ + 'test' => [ + 'name' => 'test.txt', + 'type' => 'text/plain', + 'size' => 42, + 'tmp_name' => $file, + 'error' => UPLOAD_ERR_OK, + ], + ]); + + $uploadRequest->headers->add(['content-type' => 'multipart/form-data']); + $response = $this->container->get('http_kernel')->handle($uploadRequest); + $result = json_decode($response->getContent()); + $this->assertEquals('test.txt', $result->data->store); + } +}