search_api-8.x-1.15/tests/src/Unit/Processor/AggregatedFieldsTest.php
tests/src/Unit/Processor/AggregatedFieldsTest.php
<?php namespace Drupal\Tests\search_api\Unit\Processor; use Drupal\Core\TypedData\DataDefinition; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\search_api\Datasource\DatasourceInterface; use Drupal\search_api\DataType\DataTypeInterface; use Drupal\search_api\Entity\Index; use Drupal\search_api\IndexInterface; use Drupal\search_api\Item\ItemInterface; use Drupal\search_api\Plugin\search_api\processor\AggregatedFields; use Drupal\search_api\Plugin\search_api\processor\Property\AggregatedFieldProperty; use Drupal\search_api\Processor\ProcessorInterface; use Drupal\search_api\Processor\ProcessorProperty; use Drupal\search_api\Utility\PluginHelperInterface; use Drupal\search_api\Utility\Utility; use Drupal\Tests\search_api\Unit\TestComplexDataInterface; use Drupal\Tests\UnitTestCase; /** * Tests the "Aggregated fields" processor. * * @group search_api * * @see \Drupal\search_api\Plugin\search_api\processor\AggregatedFields */ class AggregatedFieldsTest extends UnitTestCase { use TestItemsTrait; /** * The processor to be tested. * * @var \Drupal\search_api\Plugin\search_api\processor\AggregatedFields */ protected $processor; /** * A search index mock for the tests. * * @var \Drupal\search_api\IndexInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $index; /** * The field ID used in this test. * * @var string */ protected $fieldId = 'aggregated_field'; /** * The callback with which text values should be preprocessed. * * @var callable */ protected $valueCallback; /** * Creates a new processor object for use in the tests. */ protected function setUp() { parent::setUp(); $datasource = $this->createMock(DatasourceInterface::class); $datasource->expects($this->any()) ->method('getPropertyDefinitions') ->willReturn([]); $this->index = new Index([ 'datasourceInstances' => [ 'entity:test1' => $datasource, 'entity:test2' => $datasource, 'entity:test3' => $datasource, ], 'processorInstances' => [], 'field_settings' => [ 'foo' => [ 'type' => 'string', 'datasource_id' => 'entity:test1', 'property_path' => 'foo', ], 'bar' => [ 'type' => 'string', 'datasource_id' => 'entity:test1', 'property_path' => 'foo:bar', ], 'bla' => [ 'type' => 'string', 'datasource_id' => 'entity:test2', 'property_path' => 'foobaz:bla', ], 'always_empty' => [ 'type' => 'string', 'datasource_id' => 'entity:test3', 'property_path' => 'always_empty', ], 'aggregated_field' => [ 'type' => 'text', 'property_path' => 'aggregated_field', ], ], ], 'search_api_index'); $this->processor = new AggregatedFields(['#index' => $this->index], 'aggregated_field', []); $this->index->addProcessor($this->processor); $this->setUpMockContainer(); $plugin_helper = $this->createMock(PluginHelperInterface::class); $this->container->set('search_api.plugin_helper', $plugin_helper); // We want to check correct data type handling, so we need a somewhat more // complex mock-up for the datatype plugin handler. /** @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\search_api\DataType\DataTypePluginManager $data_type_manager */ $data_type_manager = $this->container->get('plugin.manager.search_api.data_type'); $data_type_manager->method('hasDefinition') ->willReturn(TRUE); $this->valueCallback = function ($value) { if (is_numeric($value)) { return $value + 1; } else { return '*' . $value; } }; $data_type = $this->createMock(DataTypeInterface::class); $data_type->method('getValue') ->willReturnCallback($this->valueCallback); $data_type_manager->method('createInstance') ->willReturnMap([ ['text', [], $data_type], ]); } /** * Tests aggregated fields of the given type. * * @param string $type * The aggregation type to test. * @param array $expected * The expected values for the two items. * @param bool $integer * (optional) TRUE if the items' normal fields should contain integers, * FALSE otherwise. * * @dataProvider aggregationTestsDataProvider */ public function testAggregation($type, array $expected, $integer = FALSE) { // Add the field configuration. $configuration = [ 'type' => $type, 'fields' => [ 'entity:test1/foo', 'entity:test1/foo:bar', 'entity:test2/foobaz:bla', 'entity:test3/always_empty', ], ]; $this->index->getField($this->fieldId)->setConfiguration($configuration); if ($integer) { $field_values = [ 'foo' => [2, 4], 'bar' => [16], 'bla' => [7], ]; } else { $field_values = [ 'foo' => ['foo', 'bar'], 'bar' => ['baz'], 'bla' => ['foobar'], ]; } $items = []; $i = 0; foreach (['entity:test1', 'entity:test2', 'entity:test3'] as $datasource_id) { $this->itemIds[$i++] = $item_id = Utility::createCombinedId($datasource_id, '1:en'); $item = \Drupal::getContainer() ->get('search_api.fields_helper') ->createItem($this->index, $item_id); foreach ([NULL, $datasource_id] as $field_datasource_id) { foreach ($this->index->getFieldsByDatasource($field_datasource_id) as $field_id => $field) { $field = clone $field; if (!empty($field_values[$field_id])) { $field->setValues($field_values[$field_id]); } $item->setField($field_id, $field); } } $item->setFieldsExtracted(TRUE); $items[$item_id] = $item; } // Add the processor's field values to the items. foreach ($items as $item) { $this->processor->addFieldValues($item); } $this->assertEquals(array_map($this->valueCallback, $expected[0]), $items[$this->itemIds[0]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 1.'); $this->assertEquals(array_map($this->valueCallback, $expected[1]), $items[$this->itemIds[1]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 2.'); $this->assertEquals(array_map($this->valueCallback, $expected[2]), $items[$this->itemIds[2]]->getField($this->fieldId)->getValues(), 'Correct aggregation for item 3.'); } /** * Provides test data for aggregation tests. * * @return array * An array containing test data sets, with each being an array of * arguments to pass to the test method. * * @see static::testAggregation() */ public function aggregationTestsDataProvider() { return [ '"Union" aggregation' => [ 'union', [ ['foo', 'bar', 'baz'], ['foobar'], [], ], ], '"Concatenation" aggregation' => [ 'concat', [ ["foo\n\nbar\n\nbaz"], ['foobar'], [''], ], ], '"Sum" aggregation' => [ 'sum', [ [22], [7], [0], ], TRUE, ], '"Count" aggregation' => [ 'count', [ [3], [1], [0], ], ], '"Maximum" aggregation' => [ 'max', [ [16], [7], [], ], TRUE, ], '"Minimum" aggregation' => [ 'min', [ [2], [7], [], ], TRUE, ], '"First" aggregation' => [ 'first', [ ['foo'], ['foobar'], [], ], ], '"Last" aggregation' => [ 'last', [ ['baz'], ['foobar'], [], ], ], ]; } /** * Tests whether the properties are correctly altered. * * @see \Drupal\search_api\Plugin\search_api\processor\AggregatedFields::getPropertyDefinitions() */ public function testGetPropertyDefinitions() { /** @var \Drupal\Core\StringTranslation\TranslationInterface $translation */ $translation = $this->getStringTranslationStub(); $this->processor->setStringTranslation($translation); // Check for added properties when no datasource is given. /** @var \Drupal\search_api\Processor\ProcessorPropertyInterface[] $properties */ $properties = $this->processor->getPropertyDefinitions(NULL); $this->assertArrayHasKey('aggregated_field', $properties, 'The "aggregated_field" property was added to the properties.'); $this->assertInstanceOf(AggregatedFieldProperty::class, $properties['aggregated_field'], 'The "aggregated_field" property has the correct class.'); $this->assertEquals('string', $properties['aggregated_field']->getDataType(), 'Correct data type set in the data definition.'); $this->assertEquals($translation->translate('Aggregated field'), $properties['aggregated_field']->getLabel(), 'Correct label set in the data definition.'); $expected_description = $translation->translate('An aggregation of multiple other fields.'); $this->assertEquals($expected_description, $properties['aggregated_field']->getDescription(), 'Correct description set in the data definition.'); // Verify that there are no properties if a datasource is given. $datasource = $this->createMock(DatasourceInterface::class); $properties = $this->processor->getPropertyDefinitions($datasource); $this->assertEmpty($properties, 'Datasource-specific properties did not get changed.'); } /** * Tests that field extraction in the processor works correctly. */ public function testFieldExtraction() { /** @var \Drupal\Tests\search_api\Unit\TestComplexDataInterface|\PHPUnit_Framework_MockObject_MockObject $object */ $object = $this->createMock(TestComplexDataInterface::class); $bar_foo_property = $this->createMock(TypedDataInterface::class); $bar_foo_property->method('getValue') ->willReturn('value3'); $bar_foo_property->method('getDataDefinition') ->willReturn(new DataDefinition()); $bar_property = $this->createMock(TestComplexDataInterface::class); $bar_property->method('get') ->willReturnMap([ ['foo', $bar_foo_property], ]); $bar_property->method('getProperties') ->willReturn([ 'foo' => TRUE, ]); $foobar_property = $this->createMock(TypedDataInterface::class); $foobar_property->method('getValue') ->willReturn('wrong_value2'); $foobar_property->method('getDataDefinition') ->willReturn(new DataDefinition()); $object->method('get') ->willReturnMap([ ['bar', $bar_property], ['foobar', $foobar_property], ]); $object->method('getProperties') ->willReturn([ 'bar' => TRUE, 'foobar' => TRUE, ]); /** @var \Drupal\search_api\IndexInterface|\PHPUnit_Framework_MockObject_MockObject $index */ $index = $this->createMock(IndexInterface::class); $fields_helper = \Drupal::getContainer()->get('search_api.fields_helper'); $field = $fields_helper->createField($index, 'aggregated_field', [ 'property_path' => 'aggregated_field', 'configuration' => [ 'type' => 'union', 'fields' => [ 'aggregated_field', 'foo', 'entity:test1/bar:foo', 'entity:test1/baz', 'entity:test2/foobar', ], ], ]); $index->method('getFields')->willReturn([ 'aggregated_field' => $field, ]); $index->method('getPropertyDefinitions') ->willReturnMap([ [ NULL, [ 'foo' => new ProcessorProperty([ 'processor_id' => 'processor1', ]), ], ], [ 'entity:test1', [ 'bar' => new DataDefinition(), 'foobar' => new DataDefinition(), ], ], ]); $processor_mock = $this->createMock(ProcessorInterface::class); $processor_mock->method('addFieldValues') ->willReturnCallback(function (ItemInterface $item) { foreach ($item->getFields(FALSE) as $field) { if ($field->getCombinedPropertyPath() == 'foo') { $field->setValues(['value4', 'value5']); } } }); $index->method('getProcessorsByStage') ->willReturnMap([ [ ProcessorInterface::STAGE_ADD_PROPERTIES, [], [ 'aggregated_field' => $this->processor, 'processor1' => $processor_mock, ], ], ]); $this->processor->setIndex($index); /** @var \Drupal\search_api\Datasource\DatasourceInterface|\PHPUnit_Framework_MockObject_MockObject $datasource */ $datasource = $this->createMock(DatasourceInterface::class); $datasource->method('getPluginId') ->willReturn('entity:test1'); $item = $fields_helper->createItem($index, 'id', $datasource); $item->setOriginalObject($object); $item->setField('aggregated_field', clone $field); $item->setField('test1', $fields_helper->createField($index, 'test1', [ 'property_path' => 'baz', 'values' => [ 'wrong_value1', ], ])); $item->setField('test2', $fields_helper->createField($index, 'test2', [ 'datasource_id' => 'entity:test1', 'property_path' => 'baz', 'values' => [ 'value1', 'value2', ], ])); $item->setFieldsExtracted(TRUE); $this->processor->addFieldValues($item); $expected = [ 'value1', 'value2', 'value3', 'value4', 'value5', ]; $actual = $item->getField('aggregated_field')->getValues(); sort($actual); $this->assertEquals($expected, $actual); } }