View source
<?php
namespace Drupal\Tests\graphql\Kernel\Framework;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Drupal\Core\Cache\Context\ContextCacheKeys;
use Drupal\Core\Render\RenderContext;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\Tests\graphql\Kernel\GraphQLTestBase;
use Prophecy\Argument;
use Drupal\graphql\Entity\Server;
use Drupal\Core\Cache\CacheableDependencyInterface;
use GraphQL\Deferred;
class ResultCacheTest extends GraphQLTestBase {
public function setUp() : void {
parent::setUp();
$schema = <<<GQL
type Query {
root: String
leakA: String
leakB: String
}
GQL;
$this
->setUpSchema($schema);
}
public function testCacheableResult() : void {
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->once())
->method('id')
->willReturn('test');
$this
->mockResolver('Query', 'root', function () use ($dummy) {
return $dummy
->id();
});
$this
->query('{ root }');
$this
->query('{ root }');
}
public function testUncacheableResult() : void {
$cacheable = $this
->getMockBuilder(CacheableDependencyInterface::class)
->setMethods([
'getCacheTags',
'getCacheMaxAge',
'getCacheContexts',
])
->getMock();
$cacheable
->expects($this
->any())
->method('getCacheTags')
->willReturn([]);
$cacheable
->expects($this
->any())
->method('getCacheMaxAge')
->willReturn(0);
$cacheable
->expects($this
->any())
->method('getCacheContexts')
->willReturn([]);
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->exactly(2))
->method('id')
->willReturn('test');
$this
->mockResolver('Query', 'root', $this->builder
->compose($this->builder
->fromValue($cacheable), $this->builder
->callback(function () use ($dummy) {
return $dummy
->id();
})));
$this
->query('{ root }');
$this
->query('{ root }');
}
public function testUncacheableResultAnnotation() : void {
$cacheable = $this
->getMockBuilder(CacheableDependencyInterface::class)
->setMethods([
'getCacheTags',
'getCacheMaxAge',
'getCacheContexts',
])
->getMock();
$cacheable
->expects($this
->any())
->method('getCacheTags')
->willReturn([]);
$cacheable
->expects($this
->any())
->method('getCacheMaxAge')
->willReturn(0);
$cacheable
->expects($this
->any())
->method('getCacheContexts')
->willReturn([]);
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->exactly(2))
->method('id')
->willReturn('test');
$this
->mockResolver('Query', 'root', $this->builder
->compose($this->builder
->fromValue($cacheable), $this->builder
->callback(function () use ($dummy) {
return $dummy
->id();
})));
$this
->query('{ root }');
$this
->query('{ root }');
}
public function testVariables() : void {
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->exactly(2))
->method('id')
->willReturn('test');
$this
->mockResolver('Query', 'root', function () use ($dummy) {
return $dummy
->id();
});
$this
->query('{ root }', NULL, [
'value' => 'a',
]);
$this
->query('{ root }', NULL, [
'value' => 'b',
]);
$this
->query('{ root }', NULL, [
'value' => 'a',
]);
}
public function testContext() : void {
$cacheable = $this
->getMockBuilder(CacheableDependencyInterface::class)
->setMethods([
'getCacheTags',
'getCacheMaxAge',
'getCacheContexts',
])
->getMock();
$cacheable
->expects($this
->any())
->method('getCacheTags')
->willReturn([]);
$cacheable
->expects($this
->any())
->method('getCacheMaxAge')
->willReturn(45);
$cacheable
->expects($this
->any())
->method('getCacheContexts')
->willReturn([
'context',
]);
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->exactly(2))
->method('id')
->willReturn('test');
$contextManager = $this
->prophesize(CacheContextsManager::class);
$this->container
->set('cache_contexts_manager', $contextManager
->reveal());
$contextManager
->assertValidTokens(Argument::any())
->willReturn(TRUE);
$hasContext = Argument::containing('context');
$hasNotContext = Argument::that(function ($arg) {
return !in_array('context', $arg);
});
$contextManager
->convertTokensToKeys($hasNotContext)
->willReturn(new ContextCacheKeys([]));
$contextKeys = $contextManager
->convertTokensToKeys($hasContext);
$this
->mockResolver('Query', 'root', $this->builder
->compose($this->builder
->fromValue($cacheable), $this->builder
->callback(function () use ($dummy) {
return $dummy
->id();
})));
$contextKeys
->willReturn(new ContextCacheKeys([
'a',
]));
$this
->query('{ root }');
$contextKeys
->willReturn(new ContextCacheKeys([
'b',
]));
$this
->query('{ root }');
$contextKeys
->willReturn(new ContextCacheKeys([
'a',
]));
$this
->query('{ root }');
}
public function testTags() : void {
$cacheable = $this
->getMockBuilder(CacheableDependencyInterface::class)
->setMethods([
'getCacheTags',
'getCacheMaxAge',
'getCacheContexts',
])
->getMock();
$cacheable
->expects($this
->any())
->method('getCacheTags')
->willReturn([
'a',
'b',
]);
$cacheable
->expects($this
->any())
->method('getCacheMaxAge')
->willReturn(45);
$cacheable
->expects($this
->any())
->method('getCacheContexts')
->willReturn([]);
$dummy = $this
->getMockBuilder(Server::class)
->disableOriginalConstructor()
->setMethods([
'id',
])
->getMock();
$dummy
->expects($this
->exactly(2))
->method('id')
->willReturn('test');
$this
->mockResolver('Query', 'root', $this->builder
->compose($this->builder
->fromValue($cacheable), $this->builder
->callback(function () use ($dummy) {
return $dummy
->id();
})));
$this
->query('{ root }');
$this->container
->get('cache_tags.invalidator')
->invalidateTags([
'a',
]);
$this
->query('{ root }');
$this->container
->get('cache_tags.invalidator')
->invalidateTags([
'c',
]);
$this
->query('{ root }');
}
public function testLeakingCacheMetadata() : void {
$renderer = $this->container
->get('renderer');
$this
->mockResolver('Query', 'leakA', function ($a, $b, $c, $d, FieldContext $field) use ($renderer) {
$el = [
'#plain_text' => 'Leak A',
'#cache' => [
'tags' => [
'a',
],
],
];
$renderContext = new RenderContext();
$value = $renderer
->executeInRenderContext($renderContext, function () use ($renderer, $el) {
return $renderer
->render($el)
->__toString();
});
if (!$renderContext
->isEmpty()) {
$field
->addCacheableDependency($renderContext
->pop());
}
return $value;
});
$this
->mockResolver('Query', 'leakB', function ($a, $b, $c, $d, FieldContext $field) use ($renderer) {
$el = [
'#plain_text' => 'Leak B',
'#cache' => [
'tags' => [
'b',
],
],
];
$renderContext = new RenderContext();
$value = $renderer
->executeInRenderContext($renderContext, function () use ($renderer, $el) {
return $renderer
->render($el)
->__toString();
});
if (!$renderContext
->isEmpty()) {
$field
->addCacheableDependency($renderContext
->pop());
}
return new Deferred(function () use ($value) {
return $value;
});
});
$query = <<<GQL
query {
leakA
leakB
}
GQL;
$metadata = $this
->defaultCacheMetaData()
->addCacheTags([
'a',
'b',
]);
$this
->assertResults($query, [], [
'leakA' => 'Leak A',
'leakB' => 'Leak B',
], $metadata);
$result = $this
->query($query);
$this
->assertEquals(200, $result
->getStatusCode());
}
}