From 5ccc298290a7914d07115ef16bafc1cbe0095001 Mon Sep 17 00:00:00 2001
From: Kalle Vuorjoki <21761-kallevu@users.noreply.drupalcode.org>
Date: Fri, 20 Sep 2024 05:59:13 +0000
Subject: [PATCH] Issue #3187670 by sokru, man-1982:
 entity.search_api_index.canonical route will produce fatal error when backend
 is down

---
 src/SearchAPI/BackendClient.php               | 20 +++--
 ...rch_api.index.test_elasticsearch_index.yml |  2 +-
 ...search_api.server.elasticsearch_server.yml |  2 +-
 .../elasticsearch_connector_test.info.yml     |  4 +-
 .../ElasticsearchConnectorBackendTest.php     | 86 +++++++++++++++++++
 5 files changed, 106 insertions(+), 8 deletions(-)
 create mode 100644 tests/src/Functional/ElasticsearchConnectorBackendTest.php

diff --git a/src/SearchAPI/BackendClient.php b/src/SearchAPI/BackendClient.php
index b8fdf7873..aecc9c1aa 100644
--- a/src/SearchAPI/BackendClient.php
+++ b/src/SearchAPI/BackendClient.php
@@ -34,6 +34,8 @@ class BackendClient implements BackendClientInterface {
    * {@inheritdoc}
    */
   public function isAvailable() {
+    // @todo If https://github.com/elastic/elasticsearch-php/issues/1308 gets
+    // fixed, we could provide more information about the error.
     try {
       $this->client->ping();
       return TRUE;
@@ -42,6 +44,10 @@ class BackendClient implements BackendClientInterface {
       $this->logger->error('%type: @message in %function (line %line of %file).', Error::decodeException($e));
       return FALSE;
     }
+    catch (\Exception $e) {
+      $this->logger->error('%type: @message in %function (line %line of %file).', Error::decodeException($e));
+      return FALSE;
+    }
   }
 
   /**
@@ -149,11 +155,15 @@ class BackendClient implements BackendClientInterface {
     $params = [
       'index' => $indexId,
     ];
-
-    // Check index exists.
-    if (!$this->client->indices()->exists($params)) {
-      $this->logger->warning('Index "%index" does not exist.', ["%index" => $indexId]);
-      return $resultSet;
+    try {
+      // Check index exists.
+      if (!$this->client->indices()->exists($params)) {
+        $this->logger->warning('Index "%index" does not exist.', ["%index" => $indexId]);
+        return $resultSet;
+      }
+    }
+    catch (\Exception $e) {
+      throw new SearchApiException(sprintf('Error: %s', $e->getMessage()), 0, $e);
     }
 
     // Build ElasticSearch query.
diff --git a/tests/modules/elasticsearch_connector_test/config/install/search_api.index.test_elasticsearch_index.yml b/tests/modules/elasticsearch_connector_test/config/install/search_api.index.test_elasticsearch_index.yml
index 229628a4e..dddc078c6 100644
--- a/tests/modules/elasticsearch_connector_test/config/install/search_api.index.test_elasticsearch_index.yml
+++ b/tests/modules/elasticsearch_connector_test/config/install/search_api.index.test_elasticsearch_index.yml
@@ -68,7 +68,7 @@ field_settings:
     datasource_id: 'entity:entity_test_mulrev_changed'
     property_path: name
     type: text
-    boost: !!float 5
+    boost: 5.0
     dependencies:
       module:
         - entity_test
diff --git a/tests/modules/elasticsearch_connector_test/config/install/search_api.server.elasticsearch_server.yml b/tests/modules/elasticsearch_connector_test/config/install/search_api.server.elasticsearch_server.yml
index 318b14d09..4aee9c213 100644
--- a/tests/modules/elasticsearch_connector_test/config/install/search_api.server.elasticsearch_server.yml
+++ b/tests/modules/elasticsearch_connector_test/config/install/search_api.server.elasticsearch_server.yml
@@ -2,7 +2,7 @@ langcode: en
 status: true
 dependencies:
   module:
-    - elasticsearch
+    - elasticsearch_connector
 id: elasticsearch_server
 name: elasticsearch_server
 description: 'Local test server'
diff --git a/tests/modules/elasticsearch_connector_test/elasticsearch_connector_test.info.yml b/tests/modules/elasticsearch_connector_test/elasticsearch_connector_test.info.yml
index d8ae76379..899928920 100644
--- a/tests/modules/elasticsearch_connector_test/elasticsearch_connector_test.info.yml
+++ b/tests/modules/elasticsearch_connector_test/elasticsearch_connector_test.info.yml
@@ -3,6 +3,8 @@ name: 'ElasticSearch Connector Test'
 description: 'ElasticSearch Connector test module'
 package: 'Search'
 dependencies:
-  - drupal:search_api_test
+  - elasticsearch_connector:elasticsearch_connector
+  - search_api:search_api_test
+  - search_api:search_api_test_example_content
 core_version_requirement: ^9 || ^10 | ^11
 hidden: true
diff --git a/tests/src/Functional/ElasticsearchConnectorBackendTest.php b/tests/src/Functional/ElasticsearchConnectorBackendTest.php
new file mode 100644
index 000000000..5d922f867
--- /dev/null
+++ b/tests/src/Functional/ElasticsearchConnectorBackendTest.php
@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\elasticsearch_connector\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Tests for situations when backend is down.
+ *
+ * @group elasticsearch_connector
+ */
+class ElasticsearchConnectorBackendTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'dblog',
+    'elasticsearch_connector',
+    'elasticsearch_connector_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Create an admin user.
+    $admin_user = $this->drupalCreateUser([
+      'access administration pages',
+      'access site reports',
+      'administer search_api',
+    ]);
+    $this->drupalLogin($admin_user);
+  }
+
+  /**
+   * Tests that no exception is thrown when visiting the Search API routes.
+   */
+  public function testSearchApiRoutes() {
+    $assert_session = $this->assertSession();
+
+    // Alter the Elasticsearch server configuration to cause failure to connect
+    // to Elasticsearch server.
+    $config = $this->config('search_api.server.elasticsearch_server');
+    $config->set('backend_config.connector_config.url', 'http://elasticsearch:9999');
+    $config->save();
+
+    // Assert "search_api.overview" route loads without errors.
+    $url = Url::fromRoute('search_api.overview');
+    $this->drupalGet($url);
+    $this->assertSession()->statusCodeEquals(200);
+    $assert_session->elementTextContains('css', '.search-api-server-elasticsearch-server .search-api-status', 'Unavailable');
+
+    // Assert "entity.search_api_server.canonical" route loads without errors.
+    $url = Url::fromRoute('entity.search_api_server.canonical', [
+      'search_api_server' => 'elasticsearch_server',
+    ]);
+    $this->drupalGet($url);
+    $this->assertSession()->statusCodeEquals(200);
+    $assert_session->pageTextContains('Local test server');
+
+    // Assert "entity.search_api_index.canonical" route loads without errors.
+    $url = Url::fromRoute('entity.search_api_index.canonical', [
+      'search_api_index' => 'test_elasticsearch_index',
+    ]);
+    $this->drupalGet($url);
+    $assert_session->statusCodeEquals(200);
+    $assert_session->pageTextContains('Test Index');
+    $assert_session->elementTextContains('css', '.search-api-index-summary--server-index-status', 'Error while checking server index status');
+
+    // Assert error produced on "search_api.overview" route is logged.
+    $this->drupalGet('/admin/reports/dblog');
+    $assert_session->pageTextContains('Elastic\Transport\Exception\NoNodeAvailableException');
+  }
+
+}
-- 
GitLab