annotate core/modules/search/tests/src/Functional/SearchMultilingualEntityTest.php @ 19:fa3358dc1485 tip

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