annotate core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents 4c8ae668cc8c
children af1871eacc83
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Tests\search\Functional;
Chris@0 4
Chris@0 5 use Drupal\field\Entity\FieldStorageConfig;
Chris@0 6 use Drupal\language\Entity\ConfigurableLanguage;
Chris@17 7 use Drupal\Tests\BrowserTestBase;
Chris@0 8
Chris@0 9 /**
Chris@0 10 * Tests entities with multilingual fields.
Chris@0 11 *
Chris@0 12 * @group search
Chris@0 13 */
Chris@17 14 class SearchMultilingualEntityTest extends BrowserTestBase {
Chris@0 15
Chris@0 16 /**
Chris@0 17 * List of searchable nodes.
Chris@0 18 *
Chris@0 19 * @var \Drupal\node\NodeInterface[]
Chris@0 20 */
Chris@0 21 protected $searchableNodes = [];
Chris@0 22
Chris@0 23 /**
Chris@0 24 * Node search plugin.
Chris@0 25 *
Chris@0 26 * @var \Drupal\node\Plugin\Search\NodeSearch
Chris@0 27 */
Chris@0 28 protected $plugin;
Chris@0 29
Chris@17 30 /**
Chris@17 31 * {@inheritdoc}
Chris@17 32 */
Chris@17 33 protected static $modules = ['language', 'locale', 'comment', 'node', 'search'];
Chris@0 34
Chris@0 35 protected function setUp() {
Chris@0 36 parent::setUp();
Chris@0 37
Chris@17 38 $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
Chris@17 39
Chris@0 40 // Create a user who can administer search, do searches, see the status
Chris@0 41 // report, and administer cron. Log in.
Chris@0 42 $user = $this->drupalCreateUser(['administer search', 'search content', 'use advanced search', 'access content', 'access site reports', 'administer site configuration']);
Chris@0 43 $this->drupalLogin($user);
Chris@0 44
Chris@0 45 // Set up the search plugin.
Chris@0 46 $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
Chris@0 47
Chris@0 48 // Check indexing counts before adding any nodes.
Chris@0 49 $this->assertIndexCounts(0, 0, 'before adding nodes');
Chris@0 50 $this->assertDatabaseCounts(0, 0, 'before adding nodes');
Chris@0 51
Chris@0 52 // Add two new languages.
Chris@0 53 ConfigurableLanguage::createFromLangcode('hu')->save();
Chris@0 54 ConfigurableLanguage::createFromLangcode('sv')->save();
Chris@0 55
Chris@0 56 // Make the body field translatable. The title is already translatable by
Chris@0 57 // definition. The parent class has already created the article and page
Chris@0 58 // content types.
Chris@0 59 $field_storage = FieldStorageConfig::loadByName('node', 'body');
Chris@0 60 $field_storage->setTranslatable(TRUE);
Chris@0 61 $field_storage->save();
Chris@0 62
Chris@0 63 // Create a few page nodes with multilingual body values.
Chris@0 64 $default_format = filter_default_format();
Chris@0 65 $nodes = [
Chris@0 66 [
Chris@0 67 'title' => 'First node en',
Chris@0 68 'type' => 'page',
Chris@0 69 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]],
Chris@0 70 'langcode' => 'en',
Chris@0 71 ],
Chris@0 72 [
Chris@0 73 'title' => 'Second node this is the English title',
Chris@0 74 'type' => 'page',
Chris@0 75 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]],
Chris@0 76 'langcode' => 'en',
Chris@0 77 ],
Chris@0 78 [
Chris@0 79 'title' => 'Third node en',
Chris@0 80 'type' => 'page',
Chris@0 81 'body' => [['value' => $this->randomMachineName(32), 'format' => $default_format]],
Chris@0 82 'langcode' => 'en',
Chris@0 83 ],
Chris@0 84 // After the third node, we don't care what the settings are. But we
Chris@0 85 // need to have at least 5 to make sure the throttling is working
Chris@0 86 // correctly. So, let's make 8 total.
Chris@0 87 [],
Chris@0 88 [],
Chris@0 89 [],
Chris@0 90 [],
Chris@0 91 [],
Chris@0 92 ];
Chris@0 93 $this->searchableNodes = [];
Chris@0 94 foreach ($nodes as $setting) {
Chris@0 95 $this->searchableNodes[] = $this->drupalCreateNode($setting);
Chris@0 96 }
Chris@0 97
Chris@0 98 // Add a single translation to the second node.
Chris@0 99 $translation = $this->searchableNodes[1]->addTranslation('hu', ['title' => 'Second node hu']);
Chris@0 100 $translation->body->value = $this->randomMachineName(32);
Chris@0 101 $this->searchableNodes[1]->save();
Chris@0 102
Chris@0 103 // Add two translations to the third node.
Chris@0 104 $translation = $this->searchableNodes[2]->addTranslation('hu', ['title' => 'Third node this is the Hungarian title']);
Chris@0 105 $translation->body->value = $this->randomMachineName(32);
Chris@0 106 $translation = $this->searchableNodes[2]->addTranslation('sv', ['title' => 'Third node sv']);
Chris@0 107 $translation->body->value = $this->randomMachineName(32);
Chris@0 108 $this->searchableNodes[2]->save();
Chris@0 109
Chris@0 110 // Verify that we have 8 nodes left to do.
Chris@0 111 $this->assertIndexCounts(8, 8, 'before updating the search index');
Chris@0 112 $this->assertDatabaseCounts(0, 0, 'before updating the search index');
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * Tests the indexing throttle and search results with multilingual nodes.
Chris@0 117 */
Chris@0 118 public function testMultilingualSearch() {
Chris@0 119 // Index only 2 nodes per cron run. We cannot do this setting in the UI,
Chris@0 120 // because it doesn't go this low.
Chris@0 121 $this->config('search.settings')->set('index.cron_limit', 2)->save();
Chris@0 122 // Get a new search plugin, to make sure it has this setting.
Chris@0 123 $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
Chris@0 124
Chris@0 125 // Update the index. This does the initial processing.
Chris@0 126 $this->plugin->updateIndex();
Chris@0 127 // Run the shutdown function. Testing is a unique case where indexing
Chris@0 128 // and searching has to happen in the same request, so running the shutdown
Chris@0 129 // function manually is needed to finish the indexing process.
Chris@0 130 search_update_totals();
Chris@0 131 $this->assertIndexCounts(6, 8, 'after updating partially');
Chris@0 132 $this->assertDatabaseCounts(2, 0, 'after updating partially');
Chris@0 133
Chris@0 134 // Now index the rest of the nodes.
Chris@0 135 // Make sure index throttle is high enough, via the UI.
Chris@0 136 $this->drupalPostForm('admin/config/search/pages', ['cron_limit' => 20], t('Save configuration'));
Chris@0 137 $this->assertEqual(20, $this->config('search.settings')->get('index.cron_limit', 100), 'Config setting was saved correctly');
Chris@0 138 // Get a new search plugin, to make sure it has this setting.
Chris@0 139 $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
Chris@0 140
Chris@0 141 $this->plugin->updateIndex();
Chris@0 142 search_update_totals();
Chris@0 143 $this->assertIndexCounts(0, 8, 'after updating fully');
Chris@0 144 $this->assertDatabaseCounts(8, 0, 'after updating fully');
Chris@0 145
Chris@0 146 // Click the reindex button on the admin page, verify counts, and reindex.
Chris@0 147 $this->drupalPostForm('admin/config/search/pages', [], t('Re-index site'));
Chris@0 148 $this->drupalPostForm(NULL, [], t('Re-index site'));
Chris@0 149 $this->assertIndexCounts(8, 8, 'after reindex');
Chris@0 150 $this->assertDatabaseCounts(8, 0, 'after reindex');
Chris@0 151 $this->plugin->updateIndex();
Chris@0 152 search_update_totals();
Chris@0 153
Chris@0 154 // Test search results.
Chris@0 155
Chris@0 156 // This should find two results for the second and third node.
Chris@0 157 $this->plugin->setSearch('English OR Hungarian', [], []);
Chris@0 158 $search_result = $this->plugin->execute();
Chris@0 159 $this->assertEqual(count($search_result), 2, 'Found two results.');
Chris@0 160 // Nodes are saved directly after each other and have the same created time
Chris@0 161 // so testing for the order is not possible.
Chris@0 162 $results = [$search_result[0]['title'], $search_result[1]['title']];
Chris@0 163 $this->assertTrue(in_array('Third node this is the Hungarian title', $results), 'The search finds the correct Hungarian title.');
Chris@0 164 $this->assertTrue(in_array('Second node this is the English title', $results), 'The search finds the correct English title.');
Chris@0 165
Chris@0 166 // Now filter for Hungarian results only.
Chris@0 167 $this->plugin->setSearch('English OR Hungarian', ['f' => ['language:hu']], []);
Chris@0 168 $search_result = $this->plugin->execute();
Chris@0 169
Chris@0 170 $this->assertEqual(count($search_result), 1, 'The search found only one result');
Chris@0 171 $this->assertEqual($search_result[0]['title'], 'Third node this is the Hungarian title', 'The search finds the correct Hungarian title.');
Chris@0 172
Chris@0 173 // Test for search with common key word across multiple languages.
Chris@0 174 $this->plugin->setSearch('node', [], []);
Chris@0 175 $search_result = $this->plugin->execute();
Chris@0 176
Chris@0 177 $this->assertEqual(count($search_result), 6, 'The search found total six results');
Chris@0 178
Chris@0 179 // Test with language filters and common key word.
Chris@0 180 $this->plugin->setSearch('node', ['f' => ['language:hu']], []);
Chris@0 181 $search_result = $this->plugin->execute();
Chris@0 182
Chris@0 183 $this->assertEqual(count($search_result), 2, 'The search found 2 results');
Chris@0 184
Chris@0 185 // Test to check for the language of result items.
Chris@0 186 foreach ($search_result as $result) {
Chris@0 187 $this->assertEqual($result['langcode'], 'hu', 'The search found the correct Hungarian result');
Chris@0 188 }
Chris@0 189
Chris@0 190 // Mark one of the nodes for reindexing, using the API function, and
Chris@0 191 // verify indexing status.
Chris@0 192 search_mark_for_reindex('node_search', $this->searchableNodes[0]->id());
Chris@0 193 $this->assertIndexCounts(1, 8, 'after marking one node to reindex via API function');
Chris@0 194
Chris@0 195 // Update the index and verify the totals again.
Chris@0 196 $this->plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
Chris@0 197 $this->plugin->updateIndex();
Chris@0 198 search_update_totals();
Chris@0 199 $this->assertIndexCounts(0, 8, 'after indexing again');
Chris@0 200
Chris@0 201 // Mark one node for reindexing by saving it, and verify indexing status.
Chris@0 202 $this->searchableNodes[1]->save();
Chris@0 203 $this->assertIndexCounts(1, 8, 'after marking one node to reindex via save');
Chris@0 204
Chris@0 205 // The request time is always the same throughout test runs. Update the
Chris@0 206 // request time to a previous time, to simulate it having been marked
Chris@0 207 // previously.
Chris@0 208 $current = REQUEST_TIME;
Chris@0 209 $old = $current - 10;
Chris@0 210 db_update('search_dataset')
Chris@0 211 ->fields(['reindex' => $old])
Chris@0 212 ->condition('reindex', $current, '>=')
Chris@0 213 ->execute();
Chris@0 214
Chris@0 215 // Save the node again. Verify that the request time on it is not updated.
Chris@0 216 $this->searchableNodes[1]->save();
Chris@0 217 $result = db_select('search_dataset', 'd')
Chris@0 218 ->fields('d', ['reindex'])
Chris@0 219 ->condition('type', 'node_search')
Chris@0 220 ->condition('sid', $this->searchableNodes[1]->id())
Chris@0 221 ->execute()
Chris@0 222 ->fetchField();
Chris@0 223 $this->assertEqual($result, $old, 'Reindex time was not updated if node was already marked');
Chris@0 224
Chris@0 225 // Add a bogus entry to the search index table using a different search
Chris@0 226 // type. This will not appear in the index status, because it is not
Chris@0 227 // managed by a plugin.
Chris@0 228 search_index('foo', $this->searchableNodes[0]->id(), 'en', 'some text');
Chris@0 229 $this->assertIndexCounts(1, 8, 'after adding a different index item');
Chris@0 230
Chris@0 231 // Mark just this "foo" index for reindexing.
Chris@0 232 search_mark_for_reindex('foo');
Chris@0 233 $this->assertIndexCounts(1, 8, 'after reindexing the other search type');
Chris@0 234
Chris@0 235 // Mark everything for reindexing.
Chris@0 236 search_mark_for_reindex();
Chris@0 237 $this->assertIndexCounts(8, 8, 'after reindexing everything');
Chris@0 238
Chris@0 239 // Clear one item from the index, but with wrong language.
Chris@0 240 $this->assertDatabaseCounts(8, 1, 'before clear');
Chris@0 241 search_index_clear('node_search', $this->searchableNodes[0]->id(), 'hu');
Chris@0 242 $this->assertDatabaseCounts(8, 1, 'after clear with wrong language');
Chris@0 243 // Clear using correct language.
Chris@0 244 search_index_clear('node_search', $this->searchableNodes[0]->id(), 'en');
Chris@0 245 $this->assertDatabaseCounts(7, 1, 'after clear with right language');
Chris@0 246 // Don't specify language.
Chris@0 247 search_index_clear('node_search', $this->searchableNodes[1]->id());
Chris@0 248 $this->assertDatabaseCounts(6, 1, 'unspecified language clear');
Chris@0 249 // Clear everything in 'foo'.
Chris@0 250 search_index_clear('foo');
Chris@0 251 $this->assertDatabaseCounts(6, 0, 'other index clear');
Chris@0 252 // Clear everything.
Chris@0 253 search_index_clear();
Chris@0 254 $this->assertDatabaseCounts(0, 0, 'complete clear');
Chris@0 255 }
Chris@0 256
Chris@0 257 /**
Chris@0 258 * Verifies the indexing status counts.
Chris@0 259 *
Chris@0 260 * @param int $remaining
Chris@0 261 * Count of remaining items to verify.
Chris@0 262 * @param int $total
Chris@0 263 * Count of total items to verify.
Chris@0 264 * @param string $message
Chris@0 265 * Message to use, something like "after updating the search index".
Chris@0 266 */
Chris@0 267 protected function assertIndexCounts($remaining, $total, $message) {
Chris@0 268 // Check status via plugin method call.
Chris@0 269 $status = $this->plugin->indexStatus();
Chris@0 270 $this->assertEqual($status['remaining'], $remaining, 'Remaining items ' . $message . ' is ' . $remaining);
Chris@0 271 $this->assertEqual($status['total'], $total, 'Total items ' . $message . ' is ' . $total);
Chris@0 272
Chris@0 273 // Check text in progress section of Search settings page. Note that this
Chris@0 274 // test avoids using
Chris@0 275 // \Drupal\Core\StringTranslation\TranslationInterface::formatPlural(), so
Chris@0 276 // it tests for fragments of text.
Chris@0 277 $indexed = $total - $remaining;
Chris@0 278 $percent = ($total > 0) ? floor(100 * $indexed / $total) : 100;
Chris@0 279 $this->drupalGet('admin/config/search/pages');
Chris@0 280 $this->assertText($percent . '% of the site has been indexed.', 'Progress percent text at top of Search settings page is correct at: ' . $message);
Chris@0 281 $this->assertText($remaining . ' item', 'Remaining text at top of Search settings page is correct at: ' . $message);
Chris@0 282
Chris@0 283 // Check text in pages section of Search settings page.
Chris@0 284 $this->assertText($indexed . ' of ' . $total . ' indexed', 'Progress text in pages section of Search settings page is correct at: ' . $message);
Chris@0 285
Chris@0 286 // Check text on status report page.
Chris@0 287 $this->drupalGet('admin/reports/status');
Chris@0 288 $this->assertText('Search index progress', 'Search status section header is present on status report page');
Chris@0 289 $this->assertText($percent . '%', 'Correct percentage is shown on status report page at: ' . $message);
Chris@0 290 $this->assertText('(' . $remaining . ' remaining)', 'Correct remaining value is shown on status report page at: ' . $message);
Chris@0 291 }
Chris@0 292
Chris@0 293 /**
Chris@0 294 * Checks actual database counts of items in the search index.
Chris@0 295 *
Chris@0 296 * @param int $count_node
Chris@0 297 * Count of node items to assert.
Chris@0 298 * @param int $count_foo
Chris@0 299 * Count of "foo" items to assert.
Chris@0 300 * @param string $message
Chris@0 301 * Message suffix to use.
Chris@0 302 */
Chris@0 303 protected function assertDatabaseCounts($count_node, $count_foo, $message) {
Chris@0 304 // Count number of distinct nodes by ID.
Chris@0 305 $results = db_select('search_dataset', 'i')
Chris@0 306 ->fields('i', ['sid'])
Chris@0 307 ->condition('type', 'node_search')
Chris@0 308 ->groupBy('sid')
Chris@0 309 ->execute()
Chris@0 310 ->fetchCol();
Chris@0 311 $this->assertEqual($count_node, count($results), 'Node count was ' . $count_node . ' for ' . $message);
Chris@0 312
Chris@0 313 // Count number of "foo" records.
Chris@0 314 $results = db_select('search_dataset', 'i')
Chris@0 315 ->fields('i', ['sid'])
Chris@0 316 ->condition('type', 'foo')
Chris@0 317 ->execute()
Chris@0 318 ->fetchCol();
Chris@0 319 $this->assertEqual($count_foo, count($results), 'Foo count was ' . $count_foo . ' for ' . $message);
Chris@0 320
Chris@0 321 }
Chris@0 322
Chris@0 323 }