3 namespace Drupal\Tests\views\Unit\Plugin\query;
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\Core\Entity\EntityStorageInterface;
7 use Drupal\Core\Entity\EntityType;
8 use Drupal\Core\Entity\EntityTypeManagerInterface;
9 use Drupal\Tests\UnitTestCase;
10 use Drupal\views\Plugin\views\query\Sql;
11 use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
12 use Drupal\views\ResultRow;
13 use Drupal\views\ViewEntityInterface;
14 use Drupal\views\ViewExecutable;
15 use Drupal\views\ViewsData;
16 use Symfony\Component\DependencyInjection\ContainerBuilder;
19 * @coversDefaultClass \Drupal\views\Plugin\views\query\Sql
23 class SqlTest extends UnitTestCase {
26 * @covers ::getCacheTags
27 * @covers ::getAllEntities
29 public function testGetCacheTags() {
30 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
31 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
33 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
37 $view->result = $result;
39 // Add a row with an entity.
40 $row = new ResultRow();
41 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
42 $prophecy->getCacheTags()->willReturn(['entity_test:123']);
43 $entity = $prophecy->reveal();
44 $row->_entity = $entity;
47 $view->result = $result;
49 // Add a row with an entity and a relationship entity.
50 $row = new ResultRow();
51 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
52 $prophecy->getCacheTags()->willReturn(['entity_test:124']);
53 $entity = $prophecy->reveal();
54 $row->_entity = $entity;
56 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
57 $prophecy->getCacheTags()->willReturn(['entity_test:125']);
58 $entity = $prophecy->reveal();
59 $row->_relationship_entities[] = $entity;
60 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
61 $prophecy->getCacheTags()->willReturn(['entity_test:126']);
62 $entity = $prophecy->reveal();
63 $row->_relationship_entities[] = $entity;
66 $view->result = $result;
68 $this->assertEquals(['entity_test:123', 'entity_test:124', 'entity_test:125', 'entity_test:126'], $query->getCacheTags());
72 * @covers ::getCacheTags
73 * @covers ::getAllEntities
75 public function testGetCacheMaxAge() {
76 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
77 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
79 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
84 // Add a row with an entity.
85 $row = new ResultRow();
86 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
87 $prophecy->getCacheMaxAge()->willReturn(10);
88 $entity = $prophecy->reveal();
90 $row->_entity = $entity;
91 $view->result[] = $row;
93 // Add a row with an entity and a relationship entity.
94 $row = new ResultRow();
95 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
96 $prophecy->getCacheMaxAge()->willReturn(20);
97 $entity = $prophecy->reveal();
98 $row->_entity = $entity;
100 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
101 $prophecy->getCacheMaxAge()->willReturn(30);
102 $entity = $prophecy->reveal();
103 $row->_relationship_entities[] = $entity;
104 $prophecy = $this->prophesize('Drupal\Core\Entity\EntityInterface');
105 $prophecy->getCacheMaxAge()->willReturn(40);
106 $entity = $prophecy->reveal();
107 $row->_relationship_entities[] = $entity;
109 $this->assertEquals(10, $query->getCacheMaxAge());
113 * Sets up the views data in the container.
115 * @param \Drupal\views\ViewsData $views_data
118 protected function setupViewsData(ViewsData $views_data) {
119 $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
120 $container->set('views.views_data', $views_data);
121 \Drupal::setContainer($container);
125 * Sets up the entity type manager in the container.
127 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
128 * The entity type manager.
130 protected function setupEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) {
131 $container = \Drupal::hasContainer() ? \Drupal::getContainer() : new ContainerBuilder();
132 $container->set('entity_type.manager', $entity_type_manager);
133 $container->set('entity.manager', $entity_type_manager);
134 \Drupal::setContainer($container);
138 * Sets up some test entity types and corresponding views data.
140 * @param \Drupal\Core\Entity\EntityInterface[][] $entities_by_type
141 * Test entities keyed by entity type and entity ID.
142 * @param \Drupal\Core\Entity\EntityInterface[][] $entity_revisions_by_type
143 * Test entities keyed by entity type and revision ID.
145 * @return \Prophecy\Prophecy\ObjectProphecy
147 protected function setupEntityTypes($entities_by_type = [], $entity_revisions_by_type = []) {
148 $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
149 $entity_type0 = new EntityType([
152 'base_table' => 'entity_first',
153 'revision_table' => 'entity_first__revision',
159 $entity_type1 = new EntityType([
162 'base_table' => 'entity_second',
163 'revision_table' => 'entity_second__revision',
170 $entity_type_manager->getDefinitions()->willReturn([
171 'first' => $entity_type0,
172 'second' => $entity_type1,
173 'base_table' => 'entity_second',
176 $entity_type_manager->getDefinition('first')->willReturn($entity_type0);
177 $entity_type_manager->getDefinition('second')->willReturn($entity_type1);
179 // Setup the views data corresponding to the entity types.
180 $views_data = $this->prophesize(ViewsData::class);
181 $views_data->get('entity_first')->willReturn([
183 'entity type' => 'first',
184 'entity revision' => FALSE,
187 $views_data->get('entity_first__revision')->willReturn([
189 'entity type' => 'first',
190 'entity revision' => TRUE,
193 $views_data->get('entity_second')->willReturn([
195 'entity type' => 'second',
196 'entity revision' => FALSE,
199 $views_data->get('entity_second__revision')->willReturn([
201 'entity type' => 'second',
202 'entity revision' => TRUE,
205 $views_data->get('entity_first_field_data')->willReturn([
207 'entity type' => 'first',
208 'entity revision' => FALSE,
211 $this->setupViewsData($views_data->reveal());
213 // Setup the loading of entities and entity revisions.
215 'first' => $this->prophesize(EntityStorageInterface::class),
216 'second' => $this->prophesize(EntityStorageInterface::class),
219 foreach ($entities_by_type as $entity_type_id => $entities) {
220 foreach ($entities as $entity_id => $entity) {
221 $entity_storages[$entity_type_id]->load($entity_id)->willReturn($entity);
223 $entity_storages[$entity_type_id]->loadMultiple(array_keys($entities))->willReturn($entities);
226 foreach ($entity_revisions_by_type as $entity_type_id => $entity_revisions) {
227 foreach ($entity_revisions as $revision_id => $revision) {
228 $entity_storages[$entity_type_id]->loadRevision($revision_id)->willReturn($revision);
232 $entity_type_manager->getStorage('first')->willReturn($entity_storages['first']);
233 $entity_type_manager->getStorage('second')->willReturn($entity_storages['second']);
235 $this->setupEntityTypeManager($entity_type_manager->reveal());
237 return $entity_type_manager;
241 * @covers ::loadEntities
242 * @covers ::assignEntitiesToResult
244 public function testLoadEntitiesWithEmptyResult() {
245 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
246 $view_entity = $this->prophesize(ViewEntityInterface::class);
247 $view_entity->get('base_table')->willReturn('entity_first');
248 $view_entity->get('base_field')->willReturn('id');
249 $view->storage = $view_entity->reveal();
251 $entity_type_manager = $this->setupEntityTypes();
253 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
254 $query->view = $view;
257 $query->addField('entity_first', 'id', 'id');
258 $query->loadEntities($result);
259 $this->assertEmpty($result);
263 * @covers ::loadEntities
264 * @covers ::assignEntitiesToResult
266 public function testLoadEntitiesWithNoRelationshipAndNoRevision() {
267 $view = $this->prophesize('Drupal\views\ViewExecutable')->reveal();
268 $view_entity = $this->prophesize(ViewEntityInterface::class);
269 $view_entity->get('base_table')->willReturn('entity_first');
270 $view_entity->get('base_field')->willReturn('id');
271 $view->storage = $view_entity->reveal();
275 1 => $this->prophesize(EntityInterface::class)->reveal(),
276 2 => $this->prophesize(EntityInterface::class)->reveal(),
279 $entity_type_manager = $this->setupEntityTypes($entities);
281 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
282 $query->view = $view;
285 $result[] = new ResultRow([
288 // Note: Let the same entity be returned multiple times, for example to
289 // support the translation usecase.
290 $result[] = new ResultRow([
293 $result[] = new ResultRow([
297 $query->addField('entity_first', 'id', 'id');
298 $query->loadEntities($result);
300 $this->assertSame($entities['first'][1], $result[0]->_entity);
301 $this->assertSame($entities['first'][2], $result[1]->_entity);
302 $this->assertSame($entities['first'][2], $result[2]->_entity);
306 * Create a view with a relationship.
308 protected function setupViewWithRelationships(ViewExecutable $view, $base = 'entity_second') {
309 // We don't use prophecy, because prophecy enforces methods.
310 $relationship = $this->getMockBuilder(RelationshipPluginBase::class)->disableOriginalConstructor()->getMock();
311 $relationship->definition['base'] = $base;
312 $relationship->tableAlias = $base;
313 $relationship->alias = $base;
315 $view->relationship[$base] = $relationship;
319 * @covers ::loadEntities
320 * @covers ::assignEntitiesToResult
322 public function testLoadEntitiesWithRelationship() {
323 // We don't use prophecy, because prophecy enforces methods.
324 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
325 $this->setupViewWithRelationships($view);
327 $view_entity = $this->prophesize(ViewEntityInterface::class);
328 $view_entity->get('base_table')->willReturn('entity_first');
329 $view_entity->get('base_field')->willReturn('id');
330 $view->storage = $view_entity->reveal();
334 1 => $this->prophesize(EntityInterface::class)->reveal(),
335 2 => $this->prophesize(EntityInterface::class)->reveal(),
338 11 => $this->prophesize(EntityInterface::class)->reveal(),
339 12 => $this->prophesize(EntityInterface::class)->reveal(),
342 $entity_type_manager = $this->setupEntityTypes($entities);
344 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
345 $query->view = $view;
348 $result[] = new ResultRow([
350 'entity_second__id' => 11,
352 // Provide an explicit NULL value, to test the case of a non required
354 $result[] = new ResultRow([
356 'entity_second__id' => NULL,
358 $result[] = new ResultRow([
360 'entity_second__id' => 12,
363 $query->addField('entity_first', 'id', 'id');
364 $query->addField('entity_second', 'id', 'entity_second__id');
365 $query->loadEntities($result);
367 $this->assertSame($entities['first'][1], $result[0]->_entity);
368 $this->assertSame($entities['first'][2], $result[1]->_entity);
369 $this->assertSame($entities['first'][2], $result[2]->_entity);
371 $this->assertSame($entities['second'][11], $result[0]->_relationship_entities['entity_second']);
372 $this->assertEquals([], $result[1]->_relationship_entities);
373 $this->assertSame($entities['second'][12], $result[2]->_relationship_entities['entity_second']);
377 * @covers ::loadEntities
378 * @covers ::assignEntitiesToResult
380 public function testLoadEntitiesWithNonEntityRelationship() {
381 // We don't use prophecy, because prophecy enforces methods.
382 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
383 $this->setupViewWithRelationships($view, 'entity_first_field_data');
385 $view_entity = $this->prophesize(ViewEntityInterface::class);
386 $view_entity->get('base_table')->willReturn('entity_first');
387 $view_entity->get('base_field')->willReturn('id');
388 $view->storage = $view_entity->reveal();
392 1 => $this->prophesize(EntityInterface::class)->reveal(),
393 2 => $this->prophesize(EntityInterface::class)->reveal(),
396 $entity_type_manager = $this->setupEntityTypes($entities);
398 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
399 $query->view = $view;
402 $result[] = new ResultRow([
405 $result[] = new ResultRow([
409 $query->addField('entity_first', 'id', 'id');
410 $query->loadEntities($result);
411 $entity_information = $query->getEntityTableInfo();
413 $this->assertSame($entities['first'][1], $result[0]->_entity);
414 $this->assertSame($entities['first'][2], $result[1]->_entity);
416 $this->assertEquals([], $result[0]->_relationship_entities);
417 $this->assertEquals([], $result[1]->_relationship_entities);
419 // This is an entity table and should be in $entity_information.
420 $this->assertContains('first', array_keys($entity_information));
421 // This is not an entity table and should not be in $entity_information.
422 $this->assertNotContains('entity_first_field_data__entity_first_field_data', array_keys($entity_information));
426 * @covers ::loadEntities
427 * @covers ::assignEntitiesToResult
429 public function testLoadEntitiesWithRevision() {
430 // We don't use prophecy, because prophecy enforces methods.
431 $view = $this->getMockBuilder(ViewExecutable::class)
432 ->disableOriginalConstructor()
435 $view_entity = $this->prophesize(ViewEntityInterface::class);
436 $view_entity->get('base_table')->willReturn('entity_first__revision');
437 $view_entity->get('base_field')->willReturn('vid');
438 $view->storage = $view_entity->reveal();
440 $entity_revisions = [
442 1 => $this->prophesize(EntityInterface::class)->reveal(),
443 3 => $this->prophesize(EntityInterface::class)->reveal(),
446 $entity_type_manager = $this->setupEntityTypes([], $entity_revisions);
448 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
449 $query->view = $view;
452 $result[] = new ResultRow([
455 $result[] = new ResultRow([
458 $result[] = new ResultRow([
462 $query->addField('entity_first__revision', 'vid', 'vid');
463 $query->loadEntities($result);
465 $this->assertSame($entity_revisions['first'][1], $result[0]->_entity);
466 $this->assertSame($entity_revisions['first'][1], $result[1]->_entity);
467 $this->assertSame($entity_revisions['first'][3], $result[2]->_entity);
471 * @covers ::loadEntities
472 * @covers ::assignEntitiesToResult
474 public function testLoadEntitiesWithRevisionOfSameEntityType() {
475 // We don't use prophecy, because prophecy enforces methods.
476 $view = $this->getMockBuilder(ViewExecutable::class)
477 ->disableOriginalConstructor()
479 $this->setupViewWithRelationships($view, 'entity_first__revision');
481 $view_entity = $this->prophesize(ViewEntityInterface::class);
482 $view_entity->get('base_table')->willReturn('entity_first');
483 $view_entity->get('base_field')->willReturn('id');
484 $view->storage = $view_entity->reveal();
488 1 => $this->prophesize(EntityInterface::class)->reveal(),
489 2 => $this->prophesize(EntityInterface::class)->reveal(),
492 $entity_revisions = [
494 1 => $this->prophesize(EntityInterface::class)->reveal(),
495 2 => $this->prophesize(EntityInterface::class)->reveal(),
496 3 => $this->prophesize(EntityInterface::class)->reveal(),
499 $entity_type_manager = $this->setupEntityTypes($entity, $entity_revisions);
501 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
502 $query->view = $view;
505 $result[] = new ResultRow([
507 'entity_first__revision__vid' => 1,
509 $result[] = new ResultRow([
511 'entity_first__revision__vid' => 2,
513 $result[] = new ResultRow([
515 'entity_first__revision__vid' => 3,
518 $query->addField('entity_first', 'id', 'id');
519 $query->addField('entity_first__revision', 'vid', 'entity_first__revision__vid');
520 $query->loadEntities($result);
522 $this->assertSame($entity['first'][1], $result[0]->_entity);
523 $this->assertSame($entity['first'][2], $result[1]->_entity);
524 $this->assertSame($entity['first'][2], $result[2]->_entity);
525 $this->assertSame($entity_revisions['first'][1], $result[0]->_relationship_entities['entity_first__revision']);
526 $this->assertSame($entity_revisions['first'][2], $result[1]->_relationship_entities['entity_first__revision']);
527 $this->assertSame($entity_revisions['first'][3], $result[2]->_relationship_entities['entity_first__revision']);
531 * @covers ::loadEntities
532 * @covers ::assignEntitiesToResult
534 public function testLoadEntitiesWithRelationshipAndRevision() {
535 // We don't use prophecy, because prophecy enforces methods.
536 $view = $this->getMockBuilder(ViewExecutable::class)->disableOriginalConstructor()->getMock();
537 $this->setupViewWithRelationships($view);
539 $view_entity = $this->prophesize(ViewEntityInterface::class);
540 $view_entity->get('base_table')->willReturn('entity_first__revision');
541 $view_entity->get('base_field')->willReturn('vid');
542 $view->storage = $view_entity->reveal();
546 11 => $this->prophesize(EntityInterface::class)->reveal(),
547 12 => $this->prophesize(EntityInterface::class)->reveal(),
550 $entity_revisions = [
552 1 => $this->prophesize(EntityInterface::class)->reveal(),
553 3 => $this->prophesize(EntityInterface::class)->reveal(),
556 $entity_type_manager = $this->setupEntityTypes($entities, $entity_revisions);
558 $query = new Sql([], 'sql', [], $entity_type_manager->reveal());
559 $query->view = $view;
562 $result[] = new ResultRow([
564 'entity_second__id' => 11,
566 // Provide an explicit NULL value, to test the case of a non required
568 $result[] = new ResultRow([
570 'entity_second__id' => NULL,
572 $result[] = new ResultRow([
574 'entity_second__id' => 12,
577 $query->addField('entity_first__revision', 'vid', 'vid');
578 $query->addField('entity_second', 'id', 'entity_second__id');
579 $query->loadEntities($result);
581 $this->assertSame($entity_revisions['first'][1], $result[0]->_entity);
582 $this->assertSame($entity_revisions['first'][1], $result[1]->_entity);
583 $this->assertSame($entity_revisions['first'][3], $result[2]->_entity);
585 $this->assertSame($entities['second'][11], $result[0]->_relationship_entities['entity_second']);
586 $this->assertEquals([], $result[1]->_relationship_entities);
587 $this->assertSame($entities['second'][12], $result[2]->_relationship_entities['entity_second']);