graphql_core_schema-1.0.x-dev/tests/src/Kernel/SchemaExtension/ViewsExtensionTest.php
tests/src/Kernel/SchemaExtension/ViewsExtensionTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\graphql_core_schema\Kernel\SchemaExtension;
use Drupal\Core\PageCache\ChainRequestPolicy;
use Drupal\Core\PageCache\RequestPolicy\NoSessionOpen;
use Drupal\node\Entity\Node;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\node\Entity\NodeType;
use Drupal\node\NodeInterface;
use Drupal\graphql\Cache\RequestPolicy\GetOnly;
use Drupal\Tests\graphql_core_schema\Kernel\CoreComposableKernelTestBase;
use Drupal\Tests\graphql\Traits\DataProducerExecutionTrait;
use Drupal\Tests\graphql\Traits\HttpRequestTrait;
use Drupal\Tests\graphql\Traits\MockingTrait;
use Drupal\Tests\graphql\Traits\QueryFileTrait;
use Drupal\Tests\graphql\Traits\QueryResultAssertionTrait;
use Drupal\Tests\graphql\Traits\SchemaPrinterTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Prophecy\PhpUnit\ProphecyTrait;
use Drupal\graphql_core_schema\ViewsSchemaBuilder;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\StringType;
use GraphQL\Type\Definition\Type;
/**
* Tests the views extension.
*
* @group graphql_core_schema
*/
class ViewsExtensionTest extends CoreComposableKernelTestBase {
use DataProducerExecutionTrait;
use HttpRequestTrait;
use QueryFileTrait;
use QueryResultAssertionTrait;
use SchemaPrinterTrait;
use MockingTrait;
use UserCreationTrait;
use ProphecyTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'graphql_core_schema_views_test',
'views',
'views_ui',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// The default admin content view is extended with an optional node id
// contextual filter to narrow down the result set to a single node.
$this->installConfig('graphql_core_schema_views_test');
ConfigurableLanguage::create([
'id' => 'fr',
'weight' => 1,
'label' => 'French',
])->save();
NodeType::create(['type' => 'article'])->save();
NodeType::create(['type' => 'page'])->save();
for ($i = 1; $i <= 150; $i++) {
$node = Node::create([
'type' => 'article',
'title' => 'Test ' . $i,
'status' => NodeInterface::PUBLISHED,
]);
$node->save();
}
$this->server = $this
->getCoreComposableServerBuilder()
->enableValueFields()
->enableExtension('views')
->enableBaseEntityField('label')
->enableEntityType('view', ['status'])
->enableEntityType('node', [], ['article'])
->createServer();
$this->server->schema_configuration['core_composable']['extension_views']['enabled_views'] = [
'content:default' => 'content:default',
'content:page_1' => 'content:page_1',
];
$this->server->save();
$this->setUpCurrentUser([], $this->userPermissions());
}
/**
* Test views properties.
*/
public function testViewsProperties(): void {
$query = $this->getQueryFromFile('views.gql');
$rows = [];
// This query will fetch the 20 rows on page 2.
for ($i = 110; $i >= 91; $i--) {
$rows[] = [
'label' => 'Test ' . $i,
'id' => $i,
];
}
$metatags = $this->defaultCacheMetaData();
$tags = [];
for ($i = 91; $i <= 110; $i++) {
$tags[] = 'node:' . $i;
}
$metatags->addCacheTags($tags);
$this->assertResults($query, [], [
'getView' => [
'executable' => [
'execute' => [
'total_rows' => 150,
'rows' => $rows,
],
'filters' => [
[
'adminLabel' => 'Content: Title',
'adminLabelShort' => 'Content: Title',
'adminSummary' => 'exposed',
'alwaysRequired' => FALSE,
'baseId' => 'string',
'field' => 'title',
'groupInfo' => [
'defaultGroup' => 'All',
'description' => '',
'groupItems' => [],
'identifier' => '',
'label' => '',
'multiple' => FALSE,
'optional' => TRUE,
'remember' => FALSE,
'widget' => 'select',
],
'isAGroup' => FALSE,
'isExposed' => TRUE,
'noOperator' => FALSE,
'operator' => 'contains',
'options' => [
'id' => 'title',
'table' => 'node_field_data',
'field' => 'title',
'relationship' => 'none',
'group_type' => 'group',
'admin_label' => '',
'operator' => 'contains',
'value' => '',
'group' => 1,
'exposed' => TRUE,
'expose' => [
'operator_id' => 'title_op',
'label' => 'Title',
'description' => '',
'use_operator' => FALSE,
'operator' => 'title_op',
'operator_limit_selection' => FALSE,
'operator_list' => [],
'identifier' => 'title',
'required' => FALSE,
'remember' => FALSE,
'multiple' => FALSE,
'remember_roles' => [
'authenticated' => 'authenticated',
],
'placeholder' => '',
],
'is_grouped' => FALSE,
'group_info' => [
'label' => '',
'description' => '',
'identifier' => '',
'optional' => TRUE,
'widget' => 'select',
'multiple' => FALSE,
'remember' => FALSE,
'default_group' => 'All',
'default_group_multiple' => [],
'group_items' => [],
],
'entity_type' => 'node',
'entity_field' => 'title',
'plugin_id' => 'string',
],
'pluginId' => 'string',
'realField' => 'title',
'table' => 'node_field_data',
'value' => '',
],
[
'adminLabel' => 'Content: Content type',
'adminLabelShort' => 'Content: Content type',
'adminSummary' => 'exposed',
'alwaysRequired' => FALSE,
'baseId' => 'bundle',
'field' => 'type',
'groupInfo' => [
'defaultGroup' => 'All',
'description' => '',
'groupItems' => [],
'identifier' => '',
'label' => '',
'multiple' => FALSE,
'optional' => TRUE,
'remember' => FALSE,
'widget' => 'select',
],
'isAGroup' => FALSE,
'isExposed' => TRUE,
'noOperator' => FALSE,
'operator' => 'in',
'options' => [
'id' => 'type',
'table' => 'node_field_data',
'field' => 'type',
'relationship' => 'none',
'group_type' => 'group',
'admin_label' => '',
'operator' => 'in',
'value' => [],
'group' => 1,
'exposed' => TRUE,
'expose' => [
'operator_id' => 'type_op',
'label' => 'Content type',
'description' => '',
'use_operator' => FALSE,
'operator' => 'type_op',
'operator_limit_selection' => FALSE,
'operator_list' => [],
'identifier' => 'type',
'required' => FALSE,
'remember' => FALSE,
'multiple' => TRUE,
'remember_roles' => [
'authenticated' => 'authenticated',
],
'reduce' => FALSE,
],
'is_grouped' => FALSE,
'group_info' => [
'label' => '',
'description' => '',
'identifier' => '',
'optional' => TRUE,
'widget' => 'select',
'multiple' => FALSE,
'remember' => FALSE,
'default_group' => 'All',
'default_group_multiple' => [],
'group_items' => [],
],
'entity_type' => 'node',
'entity_field' => 'type',
'plugin_id' => 'bundle',
],
'pluginId' => 'bundle',
'realField' => 'type',
'table' => 'node_field_data',
'value' => [],
],
[
'adminLabel' => 'Content: Published',
'adminLabelShort' => 'Content: Published',
'adminSummary' => 'grouped',
'alwaysRequired' => FALSE,
'baseId' => 'boolean',
'field' => 'status',
'groupInfo' => [
'defaultGroup' => 'All',
'description' => '',
'groupItems' => [
[
'operator' => '=',
'title' => 'Published',
'value' => '1',
],
[
'operator' => '=',
'title' => 'Unpublished',
'value' => '0',
],
],
'identifier' => 'status',
'label' => 'Published status',
'multiple' => FALSE,
'optional' => TRUE,
'remember' => FALSE,
'widget' => 'select',
],
'isAGroup' => TRUE,
'isExposed' => TRUE,
'noOperator' => FALSE,
'operator' => '=',
'options' => [
'id' => 'status',
'table' => 'node_field_data',
'field' => 'status',
'relationship' => 'none',
'group_type' => 'group',
'admin_label' => '',
'operator' => '=',
'value' => '1',
'group' => 1,
'exposed' => TRUE,
'expose' => [
'operator_id' => '',
'label' => 'Status',
'description' => '',
'use_operator' => FALSE,
'operator' => 'status_op',
'operator_limit_selection' => FALSE,
'operator_list' => [],
'identifier' => 'status',
'required' => FALSE,
'remember' => FALSE,
'multiple' => FALSE,
'remember_roles' => [
'authenticated' => 'authenticated',
],
],
'is_grouped' => TRUE,
'group_info' => [
'label' => 'Published status',
'description' => '',
'identifier' => 'status',
'optional' => TRUE,
'widget' => 'select',
'multiple' => FALSE,
'remember' => FALSE,
'default_group' => 'All',
'default_group_multiple' => [],
'group_items' => [
'1' => [
'title' => 'Published',
'operator' => '=',
'value' => '1',
],
'2' => [
'title' => 'Unpublished',
'operator' => '=',
'value' => '0',
],
],
],
'entity_type' => 'node',
'entity_field' => 'status',
'plugin_id' => 'boolean',
],
'pluginId' => 'boolean',
'realField' => 'status',
'table' => 'node_field_data',
'value' => '1',
],
[
'adminLabel' => 'Content: Translation language',
'adminLabelShort' => 'Content: Translation language',
'adminSummary' => 'exposed',
'alwaysRequired' => FALSE,
'baseId' => 'language',
'field' => 'langcode',
'groupInfo' => [
'defaultGroup' => 'All',
'description' => '',
'groupItems' => [],
'identifier' => '',
'label' => '',
'multiple' => FALSE,
'optional' => TRUE,
'remember' => FALSE,
'widget' => 'select',
],
'isAGroup' => FALSE,
'isExposed' => TRUE,
'noOperator' => FALSE,
'operator' => 'in',
'options' => [
'id' => 'langcode',
'table' => 'node_field_data',
'field' => 'langcode',
'relationship' => 'none',
'group_type' => 'group',
'admin_label' => '',
'operator' => 'in',
'value' => [],
'group' => 1,
'exposed' => TRUE,
'expose' => [
'operator_id' => 'langcode_op',
'label' => 'Language',
'description' => '',
'use_operator' => FALSE,
'operator' => 'langcode_op',
'operator_limit_selection' => FALSE,
'operator_list' => [],
'identifier' => 'langcode',
'required' => FALSE,
'remember' => FALSE,
'multiple' => FALSE,
'remember_roles' => [
'authenticated' => 'authenticated',
],
'reduce' => FALSE,
],
'is_grouped' => FALSE,
'group_info' => [
'label' => '',
'description' => '',
'identifier' => '',
'optional' => TRUE,
'widget' => 'select',
'multiple' => FALSE,
'remember' => FALSE,
'default_group' => 'All',
'default_group_multiple' => [],
'group_items' => [],
],
'entity_type' => 'node',
'entity_field' => 'langcode',
'plugin_id' => 'language',
],
'pluginId' => 'language',
'realField' => 'langcode',
'table' => 'node_field_data',
'value' => [],
],
],
'pager' => [
'perPage' => 50,
'totalItems' => 0,
],
'sorts' => [
[
'baseId' => 'standard',
'field' => 'title',
'pluginId' => 'standard',
'realField' => 'title',
],
[
'baseId' => 'standard',
'field' => 'nid',
'pluginId' => 'standard',
'realField' => 'nid',
],
],
],
'id' => 'content',
'label' => 'Content',
'status' => TRUE,
],
], $metatags);
}
/**
* Ensure filters argument exposes single and multiple input fields.
*/
public function testExecuteFiltersArgumentTypes(): void {
$schema = $this->getSchema($this->server);
/** @var \Drupal\views\ViewEntityInterface $view */
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('content');
$executable = $view->getExecutable();
$executable->setDisplay('page_1');
$typeName = ViewsSchemaBuilder::getGraphqlTypeName($executable);
/** @var \GraphQL\Type\Definition\ObjectType $viewType */
$viewType = $schema->getType($typeName);
$this->assertNotNull($viewType);
$executeField = $viewType->getField('execute');
$this->assertNotNull($executeField);
$filtersArgument = $executeField->getArg('filters');
$this->assertNotNull($filtersArgument);
$filtersType = Type::getNullableType($filtersArgument->getType());
$this->assertInstanceOf(InputObjectType::class, $filtersType);
$fields = $filtersType->getFields();
$this->assertArrayHasKey('title', $fields);
$this->assertArrayHasKey('type', $fields);
$titleFieldType = Type::getNullableType($fields['title']->getType());
$this->assertInstanceOf(StringType::class, $titleFieldType);
$typeFieldType = Type::getNullableType($fields['type']->getType());
$this->assertInstanceOf(ListOfType::class, $typeFieldType);
$this->assertInstanceOf(StringType::class, Type::getNullableType($typeFieldType->getWrappedType()));
}
/**
* Test views with a contextual filter and a query parameter.
*/
public function testViewsWithQueryParameterAndContextualFilterProperties(): void {
$query = $this->getQueryFromFile('views_contextual_and_query_pm.gql');
$rows = [];
$metadata = $this->defaultCacheMetaData();
$tags = [];
// This query will fetch 1 row on page 0.
// @see This must match the contextualFilters value in
// views_contextual_and_*.gql.
$i = 129;
$rows[] = [
'label' => 'Test ' . $i,
'id' => $i,
];
$tags[] = 'node:' . $i;
$metadata->addCacheTags($tags);
$this->assertResults($query, [], [
'getView' => [
'executable' => [
'execute' => [
'total_rows' => 1,
'rows' => $rows,
],
],
],
], $metadata);
// Test the same thing with filters instead of query parameters.
$query = $this->getQueryFromFile('views_contextual_and_filter.gql');
$this->assertResults($query, [], [
'getView' => [
'executable' => [
'execute' => [
'total_rows' => 1,
'rows' => $rows,
],
],
],
], $metadata);
}
/**
* Test views with query parameters.
*/
public function testViewsWithQueryParameterProperties(): void {
$query = $this->getQueryFromFile('views_query_pm.gql');
$rows = [];
$metadata = $this->defaultCacheMetaData();
$tags = [];
// This query will fetch the 10 rows on page 0.
for ($i = 129; $i >= 120; $i--) {
$rows[] = [
'label' => 'Test ' . $i,
'id' => $i,
];
$tags[] = 'node:' . $i;
}
$metadata->addCacheTags($tags);
$this->assertResults($query, [], [
'getView' => [
'executable' => [
'execute' => [
'total_rows' => 11,
'rows' => $rows,
],
],
],
], $metadata);
// Test the same thing with filters instead of query parameters.
$query = $this->getQueryFromFile('views_filter.gql');
$this->assertResults($query, [], [
'getView' => [
'executable' => [
'execute' => [
'total_rows' => 11,
'rows' => $rows,
],
],
],
], $metadata);
}
/**
* Configures core's cache policy.
*
* Modifies the DefaultRequestPolicy classes, which always add in the
* CommandLineOrUnsafeMethod policy which will always result in DENY in a
* Kernel test because we're running via the command line.
*
* @param int $max_age
* Max age to cache responses.
*/
protected function configureCachePolicy(int $max_age = 900): void {
$this->container->set('dynamic_page_cache_request_policy', (new ChainRequestPolicy())
->addPolicy(new GetOnly()));
$this->container->set('page_cache_request_policy', (new ChainRequestPolicy())
->addPolicy(new NoSessionOpen($this->container->get('session_configuration')))
->addPolicy(new GetOnly()));
// Turn on caching.
$this->config('system.performance')->set('cache.page.max_age', $max_age)->save();
}
/**
* Returns the default cache maximum age for the test.
*/
protected function defaultCacheMaxAge(): int {
// Views with exposed filters are not cached.
return 0;
}
/**
* Returns the default cache tags used in assertions for this test.
*
* @return string[]
* The list of cache tags.
*/
protected function defaultCacheTags(): array {
$tags = ['graphql_response'];
if (isset($this->server)) {
array_push($tags, "config:graphql.graphql_servers.{$this->server->id()}");
}
$tags[] = 'config:views.view.content';
$tags[] = 'node_list';
$tags[] = 'user_list';
$tags[] = 'user:0';
return $tags;
}
/**
* Returns the default cache contexts used in assertions for this test.
*
* @return string[]
* The list of cache contexts.
*/
protected function defaultCacheContexts(): array {
return [
'user.permissions',
'languages:language_interface',
'url.query_args',
];
}
/**
* Provides the user permissions that the test user is set up with.
*
* @return string[]
* List of user permissions.
*/
protected function userPermissions(): array {
return [
'access content',
'access content overview',
'bypass graphql access',
'administer views',
];
}
}
