]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18679 move old UTs to ITs in sonar modules
authorLéo Geoffroy <99647462+leo-geoffroy-sonarsource@users.noreply.github.com>
Tue, 14 Mar 2023 11:08:58 +0000 (12:08 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 14 Mar 2023 20:03:28 +0000 (20:03 +0000)
62 files changed:
server/sonar-server-common/src/it/java/org/sonar/server/component/index/ComponentIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/es/BulkIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/es/OneToManyResilientIndexingListenerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/es/OneToOneResilientIndexingListenerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/es/metadata/MetadataIndexIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/favorite/FavoriteUpdaterIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/issue/notification/NewIssuesNotificationIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/measure/index/ProjectMeasuresIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/metric/MetricFinderIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/platform/StartupMetadataProviderIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/platform/monitoring/DbSectionIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/qualitygate/QualityGateFinderIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/rule/DefaultRuleFinderIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/setting/DatabaseSettingLoaderIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/setting/ProjectConfigurationLoaderImplIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexerIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/webhook/AsynchronousWebHooksImplIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/webhook/SynchronousWebHooksImplIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/java/org/sonar/server/webhook/WebhookDeliveryStorageIT.java [new file with mode: 0644]
server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index.xml [new file with mode: 0644]
server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index_project.xml [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/BulkIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/OneToManyResilientIndexingListenerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/OneToOneResilientIndexingListenerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/es/metadata/MetadataIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIteratorFactoryTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/metric/MetricFinderTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/platform/monitoring/DbSectionTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/rule/DefaultRuleFinderTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/setting/DatabaseSettingLoaderTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexerTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java [deleted file]
server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java [deleted file]
server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index.xml [deleted file]
server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index_project.xml [deleted file]
sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java [deleted file]

diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/component/index/ComponentIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/component/index/ComponentIndexerIT.java
new file mode 100644 (file)
index 0000000..eda1f4e
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.component.index;
+
+import java.util.Arrays;
+import java.util.Collection;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentUpdateDto;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.ProjectIndexer;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
+
+public class ComponentIndexerIT {
+
+  private System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private DbClient dbClient = db.getDbClient();
+  private DbSession dbSession = db.getSession();
+  private ComponentIndexer underTest = new ComponentIndexer(db.getDbClient(), es.client());
+
+  @Test
+  public void test_getIndexTypes() {
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_COMPONENT);
+  }
+
+  @Test
+  public void indexOnStartup_does_nothing_if_no_projects() {
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void indexOnStartup_indexes_all_components() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project1, project2);
+  }
+
+  @Test
+  public void indexOAll_indexes_all_components() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    underTest.indexAll();
+
+    assertThatIndexContainsOnly(project1, project2);
+  }
+
+  @Test
+  public void map_fields() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project);
+    ComponentDoc doc = es.getDocuments(TYPE_COMPONENT, ComponentDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(project.uuid());
+    assertThat(doc.getKey()).isEqualTo(project.getKey());
+    assertThat(doc.getProjectUuid()).isEqualTo(project.branchUuid());
+    assertThat(doc.getName()).isEqualTo(project.name());
+  }
+
+  @Test
+  public void indexOnStartup_does_not_index_non_main_branches() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void indexOnAnalysis_indexes_project() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.indexOnAnalysis(project.uuid());
+
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void indexOnAnalysis_indexes_new_components() {
+    ComponentDto project = db.components().insertPrivateProject();
+    underTest.indexOnAnalysis(project.uuid());
+    assertThatIndexContainsOnly(project);
+
+    underTest.indexOnAnalysis(project.uuid());
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void indexOnAnalysis_updates_index_on_changes() {
+    ComponentDto project = db.components().insertPrivateProject();
+    underTest.indexOnAnalysis(project.uuid());
+    assertThatComponentHasName(project, project.name());
+
+    // modify
+    project.setName("NewName");
+    updateDb(project);
+
+    // verify that index is updated
+    underTest.indexOnAnalysis(project.uuid());
+    assertThatIndexContainsOnly(project);
+    assertThatComponentHasName(project, "NewName");
+  }
+
+  @Test
+  public void indexOnAnalysis_does_not_index_non_main_branches() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
+
+    underTest.indexOnAnalysis(branch.uuid());
+
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void do_not_update_index_on_project_tag_update() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    indexProject(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
+
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void do_not_update_index_on_permission_change() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    indexProject(project, ProjectIndexer.Cause.PERMISSION_CHANGE);
+
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void update_index_on_project_creation() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    IndexingResult result = indexProject(project, PROJECT_CREATION);
+
+    assertThatIndexContainsOnly(project);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+  }
+
+  @Test
+  public void delete_some_components() {
+    ComponentDto project = db.components().insertPrivateProject();
+    indexProject(project, PROJECT_CREATION);
+
+    underTest.delete(project.uuid(), emptySet());
+
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void delete_project() {
+    ComponentDto project = db.components().insertPrivateProject();
+    indexProject(project, PROJECT_CREATION);
+    assertThatIndexHasSize(1);
+
+    db.getDbClient().purgeDao().deleteProject(db.getSession(), project.uuid(), PROJECT, project.name(), project.getKey());
+    indexProject(project, PROJECT_DELETION);
+
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void errors_during_indexing_are_recovered() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    es.lockWrites(TYPE_COMPONENT);
+
+    IndexingResult result = indexProject(project1, PROJECT_CREATION);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+
+    // index is still read-only, fail to recover
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+    assertThat(es.countDocuments(TYPE_COMPONENT)).isZero();
+
+    es.unlockWrites(TYPE_COMPONENT);
+
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isZero();
+    assertThatIndexContainsOnly(project1);
+  }
+
+  private IndexingResult indexProject(ComponentDto project, ProjectIndexer.Cause cause) {
+    DbSession dbSession = db.getSession();
+    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
+    dbSession.commit();
+    return underTest.index(dbSession, items);
+  }
+
+  private void updateDb(ComponentDto component) {
+    ComponentUpdateDto updateComponent = ComponentUpdateDto.copyFrom(component);
+    updateComponent.setBChanged(true);
+    dbClient.componentDao().update(dbSession, updateComponent, component.qualifier());
+    dbClient.componentDao().applyBChangesForBranchUuid(dbSession, component.branchUuid());
+    dbSession.commit();
+  }
+
+  private IndexingResult recover() {
+    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
+    return underTest.index(db.getSession(), items);
+  }
+
+  private void assertThatIndexHasSize(int expectedSize) {
+    assertThat(es.countDocuments(TYPE_COMPONENT)).isEqualTo(expectedSize);
+  }
+
+  private void assertThatIndexContainsOnly(ComponentDto... expectedComponents) {
+    assertThat(es.getIds(TYPE_COMPONENT)).containsExactlyInAnyOrder(
+      Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new));
+  }
+
+  private void assertThatComponentHasName(ComponentDto component, String expectedName) {
+    SearchHit[] hits = es.client()
+      .search(EsClient.prepareSearch(TYPE_COMPONENT.getMainType())
+        .source(new SearchSourceBuilder()
+          .query(matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), expectedName))))
+      .getHits()
+      .getHits();
+    assertThat(hits)
+      .extracting(SearchHit::getId)
+      .contains(component.uuid());
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/es/BulkIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/es/BulkIndexerIT.java
new file mode 100644 (file)
index 0000000..774e3bd
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
+import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbTester;
+import org.sonar.server.es.BulkIndexer.Size;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
+
+import static java.util.Collections.emptyMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.EXCPECTED_TYPE_FAKE;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.INDEX;
+import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
+
+public class BulkIndexerIT {
+
+  private final TestSystem2 testSystem2 = new TestSystem2().setNow(1_000L);
+
+  @Rule
+  public EsTester es = EsTester.createCustom(new FakeIndexDefinition().setReplicas(1));
+  @Rule
+  public DbTester dbTester = DbTester.create(testSystem2);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Test
+  public void index_nothing() {
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
+    indexer.start();
+    indexer.stop();
+
+    assertThat(count()).isZero();
+  }
+
+  @Test
+  public void index_documents() {
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
+    indexer.start();
+    indexer.add(newIndexRequest(42));
+    indexer.add(newIndexRequest(78));
+
+    // request is not sent yet
+    assertThat(count()).isZero();
+
+    // send remaining requests
+    indexer.stop();
+    assertThat(count()).isEqualTo(2);
+  }
+
+  @Test
+  public void large_indexing() {
+    // index has one replica
+    assertThat(replicas()).isOne();
+
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.LARGE);
+    indexer.start();
+
+    // replicas are temporarily disabled
+    assertThat(replicas()).isZero();
+
+    for (int i = 0; i < 10; i++) {
+      indexer.add(newIndexRequest(i));
+    }
+    IndexingResult result = indexer.stop();
+
+    assertThat(result.isSuccess()).isTrue();
+    assertThat(result.getSuccess()).isEqualTo(10);
+    assertThat(result.getFailures()).isZero();
+    assertThat(result.getTotal()).isEqualTo(10);
+    assertThat(count()).isEqualTo(10);
+    // replicas are re-enabled
+    assertThat(replicas()).isOne();
+  }
+
+  @Test
+  public void bulk_delete() {
+    int max = 500;
+    int removeFrom = 200;
+    FakeDoc[] docs = new FakeDoc[max];
+    for (int i = 0; i < max; i++) {
+      docs[i] = FakeIndexDefinition.newDoc(i);
+    }
+    es.putDocuments(TYPE_FAKE, docs);
+    assertThat(count()).isEqualTo(max);
+
+    SearchRequest req = EsClient.prepareSearch(TYPE_FAKE)
+      .source(new SearchSourceBuilder().query(QueryBuilders.rangeQuery(FakeIndexDefinition.INT_FIELD).gte(removeFrom)));
+    BulkIndexer.delete(es.client(), TYPE_FAKE, req);
+
+    assertThat(count()).isEqualTo(removeFrom);
+  }
+
+  @Test
+  public void listener_is_called_on_successful_requests() {
+    FakeListener listener = new FakeListener();
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
+    indexer.start();
+    indexer.addDeletion(TYPE_FAKE, "foo");
+    indexer.stop();
+    assertThat(listener.calledDocIds)
+      .containsExactlyInAnyOrder(newDocId(EXCPECTED_TYPE_FAKE, "foo"));
+    assertThat(listener.calledResult.getSuccess()).isOne();
+    assertThat(listener.calledResult.getTotal()).isOne();
+  }
+
+  @Test
+  public void listener_is_called_even_if_deleting_a_doc_that_does_not_exist() {
+    FakeListener listener = new FakeListener();
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
+    indexer.start();
+    indexer.add(newIndexRequestWithDocId("foo"));
+    indexer.add(newIndexRequestWithDocId("bar"));
+    indexer.stop();
+    assertThat(listener.calledDocIds)
+      .containsExactlyInAnyOrder(newDocId(EXCPECTED_TYPE_FAKE, "foo"), newDocId(EXCPECTED_TYPE_FAKE, "bar"));
+    assertThat(listener.calledResult.getSuccess()).isEqualTo(2);
+    assertThat(listener.calledResult.getTotal()).isEqualTo(2);
+  }
+
+  @Test
+  public void listener_is_not_called_with_errors() {
+    FakeListener listener = new FakeListener();
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
+    indexer.start();
+    indexer.add(newIndexRequestWithDocId("foo"));
+    indexer.add(new IndexRequest("index_does_not_exist").id("bar").source(emptyMap()));
+    indexer.stop();
+    assertThat(listener.calledDocIds).containsExactly(newDocId(EXCPECTED_TYPE_FAKE, "foo"));
+    assertThat(listener.calledResult.getSuccess()).isOne();
+    assertThat(listener.calledResult.getTotal()).isEqualTo(2);
+  }
+
+  @Test
+  public void log_requests_when_TRACE_level_is_enabled() {
+    logTester.setLevel(LoggerLevel.TRACE);
+
+    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, new FakeListener());
+    indexer.start();
+    indexer.add(newIndexRequestWithDocId("foo"));
+    indexer.addDeletion(TYPE_FAKE, "foo");
+    indexer.add(newIndexRequestWithDocId("bar"));
+    indexer.stop();
+
+    assertThat(logTester.logs(LoggerLevel.TRACE)
+      .stream()
+      .filter(log -> log.contains("Bulk[2 index requests on fakes/_doc, 1 delete requests on fakes/_doc]"))
+      .count()).isNotZero();
+
+  }
+
+  private static class FakeListener implements IndexingListener {
+    private final List<DocId> calledDocIds = new ArrayList<>();
+    private IndexingResult calledResult;
+
+    @Override
+    public void onSuccess(List<DocId> docIds) {
+      calledDocIds.addAll(docIds);
+    }
+
+    @Override
+    public void onFinish(IndexingResult result) {
+      calledResult = result;
+    }
+  }
+
+  private long count() {
+    return es.countDocuments(IndexType.main(Index.simple("fakes"), "fake"));
+  }
+
+  private int replicas() {
+    try {
+      GetSettingsResponse settingsResp = es.client().nativeClient().indices()
+          .getSettings(new GetSettingsRequest().indices(INDEX), RequestOptions.DEFAULT);
+      return Integer.parseInt(settingsResp.getSetting(INDEX, IndexMetadata.SETTING_NUMBER_OF_REPLICAS));
+    } catch (IOException e) {
+      throw new IllegalStateException("Could not get index settings", e);
+    }
+  }
+
+  private IndexRequest newIndexRequest(int intField) {
+    return new IndexRequest(INDEX)
+      .source(Map.of(FakeIndexDefinition.INT_FIELD, intField));
+  }
+
+  private IndexRequest newIndexRequestWithDocId(String id) {
+    return new IndexRequest(INDEX)
+      .id(id)
+      .source(Map.of(FakeIndexDefinition.INT_FIELD, 42));
+  }
+
+  private static DocId newDocId(IndexType.IndexMainType mainType, String id) {
+    return new DocId(TYPE_FAKE.getIndex().getName(), mainType.getType(), id);
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/es/OneToManyResilientIndexingListenerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/es/OneToManyResilientIndexingListenerIT.java
new file mode 100644 (file)
index 0000000..f5afa0a
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.server.component.index.ComponentIndexDefinition;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
+
+public class OneToManyResilientIndexingListenerIT {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  @Test
+  public void ES_QUEUE_rows_are_deleted_when_all_docs_are_successfully_indexed() {
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
+    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
+    db.commit();
+
+    // does not contain outOfScopeItem
+    IndexingListener underTest = newListener(asList(item1, item2));
+
+    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
+    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
+    underTest.onSuccess(asList(issue1, issue2));
+    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
+
+    // onFinish deletes all items
+    IndexingResult result = new IndexingResult();
+    result.incrementSuccess().incrementRequests();
+    result.incrementSuccess().incrementRequests();
+    underTest.onFinish(result);
+
+    assertThatEsTableContainsOnly(outOfScopeItem);
+  }
+
+  @Test
+  public void ES_QUEUE_rows_are_not_deleted_on_partial_error() {
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
+    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
+    db.commit();
+
+    // does not contain outOfScopeItem
+    IndexingListener underTest = newListener(asList(item1, item2));
+
+    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
+    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
+    underTest.onSuccess(asList(issue1, issue2));
+    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
+
+    // one failure among the 2 indexing requests of issues
+    IndexingResult result = new IndexingResult();
+    result.incrementSuccess().incrementRequests();
+    result.incrementRequests();
+    underTest.onFinish(result);
+
+    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
+  }
+
+  private static DocId newDocId(IndexType.IndexRelationType indexType, String id) {
+    IndexType.IndexMainType mainType = indexType.getMainType();
+    return new DocId(mainType.getIndex().getName(), mainType.getType(), id);
+  }
+
+  private IndexingListener newListener(Collection<EsQueueDto> items) {
+    return new OneToManyResilientIndexingListener(db.getDbClient(), db.getSession(), items);
+  }
+
+  private EsQueueDto insertInQueue(IndexType indexType, String id) {
+    EsQueueDto item = EsQueueDto.create(indexType.format(), id);
+    db.getDbClient().esQueueDao().insert(db.getSession(), singletonList(item));
+    return item;
+  }
+
+  private void assertThatEsTableContainsOnly(EsQueueDto... expected) {
+    try (DbSession otherSession = db.getDbClient().openSession(false)) {
+      List<String> uuidsInDb = db.getDbClient().esQueueDao().selectForRecovery(otherSession, Long.MAX_VALUE, 10)
+        .stream().map(EsQueueDto::getUuid).collect(toList());
+      String[] expectedUuids = Arrays.stream(expected).map(EsQueueDto::getUuid).toArray(String[]::new);
+      assertThat(uuidsInDb).containsExactlyInAnyOrder(expectedUuids);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/es/OneToOneResilientIndexingListenerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/es/OneToOneResilientIndexingListenerIT.java
new file mode 100644 (file)
index 0000000..92c474d
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.es.EsQueueDto;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
+
+public class OneToOneResilientIndexingListenerIT {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  @Test
+  public void onSuccess_deletes_rows_from_ES_QUEUE_table() {
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "bar");
+    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "baz");
+    db.commit();
+
+    IndexingListener underTest = newListener(asList(item1, item2, item3));
+
+    underTest.onSuccess(emptyList());
+    assertThatEsTableContainsOnly(item1, item2, item3);
+
+    underTest.onSuccess(asList(toDocId(item1), toDocId(item3)));
+    assertThatEsTableContainsOnly(item2);
+
+    // onFinish does nothing
+    underTest.onFinish(new IndexingResult());
+    assertThatEsTableContainsOnly(item2);
+  }
+
+  /**
+   * ES_QUEUE can contain multiple times the same document, for instance
+   * when an issue has been updated multiple times in a row without
+   * being successfully indexed.
+   * Elasticsearch response does not make difference between the different
+   * occurrences (and nevertheless it would be useless). So all the
+   * occurrences are marked as successfully indexed if a single request
+   * passes.
+   */
+  @Test
+  public void onSuccess_deletes_all_the_rows_with_same_doc_id() {
+    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
+    // same id as item1
+    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, item1.getDocId());
+    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "bar");
+    db.commit();
+
+    IndexingListener underTest = newListener(asList(item1, item2, item3));
+
+    underTest.onSuccess(asList(toDocId(item1)));
+    assertThatEsTableContainsOnly(item3);
+  }
+
+  private static DocId toDocId(EsQueueDto dto) {
+    IndexType.SimpleIndexMainType mainType = IndexType.parseMainType(dto.getDocType());
+    return new DocId(mainType.getIndex(), mainType.getType(), dto.getDocId());
+  }
+
+  private IndexingListener newListener(Collection<EsQueueDto> items) {
+    return new OneToOneResilientIndexingListener(db.getDbClient(), db.getSession(), items);
+  }
+
+  private EsQueueDto insertInQueue(IndexType indexType, String id) {
+    EsQueueDto item = EsQueueDto.create(indexType.format(), id);
+    db.getDbClient().esQueueDao().insert(db.getSession(), singletonList(item));
+    return item;
+  }
+
+  private void assertThatEsTableContainsOnly(EsQueueDto... expected) {
+    try (DbSession otherSession = db.getDbClient().openSession(false)) {
+      List<String> uuidsInDb = db.getDbClient().esQueueDao().selectForRecovery(otherSession, Long.MAX_VALUE, 10)
+        .stream().map(EsQueueDto::getUuid).collect(toList());
+      String[] expectedUuids = Arrays.stream(expected).map(EsQueueDto::getUuid).toArray(String[]::new);
+      assertThat(uuidsInDb).containsExactlyInAnyOrder(expectedUuids);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/es/metadata/MetadataIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/es/metadata/MetadataIndexIT.java
new file mode 100644 (file)
index 0000000..1c48ebb
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.es.metadata;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Locale;
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexType.IndexMainType;
+import org.sonar.server.es.newindex.FakeIndexDefinition;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@RunWith(DataProviderRunner.class)
+public class MetadataIndexIT {
+
+  @Rule
+  public EsTester es = EsTester.createCustom(new MetadataIndexDefinitionBridge(), new FakeIndexDefinition());
+  private final MetadataIndex underTest = new MetadataIndexImpl(es.client());
+  private final String indexName = randomAlphabetic(20).toLowerCase(Locale.ENGLISH);
+  private final Index index = new Random().nextBoolean() ? Index.simple(indexName) : Index.withRelations(indexName);
+
+  @Test
+  @UseDataProvider("mainOrRelationType")
+  public void type_should_be_not_initialized_by_default(IndexType indexType) {
+    assertThat(underTest.getInitialized(indexType)).isFalse();
+  }
+
+  @Test
+  @UseDataProvider("mainOrRelationType")
+  public void type_should_be_initialized_after_explicitly_set_to_initialized(IndexType indexType) {
+
+    underTest.setInitialized(indexType, true);
+    assertThat(underTest.getInitialized(indexType)).isTrue();
+  }
+
+  @DataProvider
+  public static Object[][] mainOrRelationType() {
+    IndexMainType mainType = IndexType.main(Index.withRelations("examples"), "example");
+    return new Object[][] {
+      {mainType},
+      {IndexType.relation(mainType, "doo")}
+    };
+  }
+
+  @Test
+  public void hash_should_be_empty_by_default() {
+    assertThat(underTest.getHash(index)).isEmpty();
+  }
+
+  @Test
+  public void hash_should_be_able_to_be_automatically_set() {
+    String hash = randomAlphanumeric(20);
+    underTest.setHash(index, hash);
+    assertThat(underTest.getHash(index)).hasValue(hash);
+  }
+
+  @Test
+  public void database_metadata_are_empty_if_absent_from_index() {
+    assertThat(underTest.getDbVendor()).isNotPresent();
+  }
+
+  @Test
+  public void database_metadata_are_present_from_index() {
+    underTest.setDbMetadata("postgres");
+
+    assertThat(underTest.getDbVendor()).hasValue("postgres");
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/favorite/FavoriteUpdaterIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/favorite/FavoriteUpdaterIT.java
new file mode 100644 (file)
index 0000000..8a7c4ae
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.favorite;
+
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyQuery;
+import org.sonar.db.user.UserDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class FavoriteUpdaterIT {
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private DbClient dbClient = db.getDbClient();
+  private DbSession dbSession = db.getSession();
+
+  private FavoriteUpdater underTest = new FavoriteUpdater(dbClient);
+
+  @Test
+  public void put_favorite() {
+    ComponentDto project = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
+    assertNoFavorite(project, user);
+
+    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true);
+
+    assertFavorite(project, user);
+  }
+
+  @Test
+  public void do_nothing_when_no_user() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.add(dbSession, project, null, null,true);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setComponentUuid(project.uuid())
+      .build(), dbSession)).isEmpty();
+  }
+
+  @Test
+  public void do_not_add_favorite_when_already_100_favorite_projects() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getUuid(), user.getName()));
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .build(), dbSession)).hasSize(100);
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), false);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .build(), dbSession)).hasSize(100);
+  }
+
+  @Test
+  public void do_not_add_favorite_when_already_100_favorite_portfolios() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(),
+      user.getUuid(), user.getLogin()));
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .build(), dbSession)).hasSize(100);
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), false);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .build(), dbSession)).hasSize(100);
+  }
+
+  @Test
+  public void fail_when_more_than_100_projects_favorites() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(),
+      user.getUuid(), user.getLogin()));
+    ComponentDto project = db.components().insertPrivateProject();
+
+    assertThatThrownBy(() -> underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("You cannot have more than 100 favorites on components with qualifier 'TRK'");
+  }
+
+  @Test
+  public void fail_when_adding_existing_favorite() {
+    ComponentDto project = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
+    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true);
+    assertFavorite(project, user);
+
+    assertThatThrownBy(() -> underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage(String.format("Component '%s' (uuid: %s) is already a favorite", project.getKey(), project.uuid()));
+  }
+
+  private void assertFavorite(ComponentDto project, UserDto user) {
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .setComponentUuid(project.uuid())
+      .build(), dbSession)).hasSize(1);
+  }
+
+  private void assertNoFavorite(ComponentDto project, UserDto user) {
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserUuid(user.getUuid())
+      .setComponentUuid(project.uuid())
+      .build(), dbSession)).isEmpty();
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIndexerIT.java
new file mode 100644 (file)
index 0000000..dae2aeb
--- /dev/null
@@ -0,0 +1,625 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.assertj.core.api.Assertions;
+import org.elasticsearch.search.SearchHit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.es.StartupIndexer;
+import org.sonar.server.permission.index.AuthorizationScope;
+import org.sonar.server.permission.index.IndexPermissions;
+import org.sonar.server.security.SecurityStandards;
+import org.sonar.server.security.SecurityStandards.SQCategory;
+import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.server.issue.IssueDocTesting.newDoc;
+import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
+import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
+
+public class IssueIndexerIT {
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create();
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final IssueIndexer underTest = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
+
+  @Test
+  public void test_getIndexTypes() {
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ISSUE);
+  }
+
+  @Test
+  public void test_getAuthorizationScope() {
+    AuthorizationScope scope = underTest.getAuthorizationScope();
+    assertThat(scope.getIndexType().getIndex()).isEqualTo(IssueIndexDefinition.DESCRIPTOR);
+    assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
+
+    Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
+    IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
+    IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
+    assertThat(projectPredicate.test(project)).isTrue();
+    assertThat(projectPredicate.test(file)).isFalse();
+  }
+
+  @Test
+  public void indexOnStartup_scrolls_db_and_adds_all_issues_to_index() {
+    IssueDto issue1 = db.issues().insert();
+    IssueDto issue2 = db.issues().insert();
+
+    underTest.indexAllIssues();
+
+    assertThatIndexHasOnly(issue1, issue2);
+  }
+
+  @Test
+  public void verify_indexed_fields() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
+    IssueDto issue = db.issues().insert(rule, project, file);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.assigneeUuid()).isEqualTo(issue.getAssigneeUuid());
+    assertThat(doc.authorLogin()).isEqualTo(issue.getAuthorLogin());
+    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
+    assertThat(doc.projectUuid()).isEqualTo(project.uuid());
+    assertThat(doc.branchUuid()).isEqualTo(project.uuid());
+    assertThat(doc.isMainBranch()).isTrue();
+    assertThat(doc.closeDate()).isEqualTo(issue.getIssueCloseDate());
+    assertThat(doc.creationDate()).isEqualToIgnoringMillis(issue.getIssueCreationDate());
+    assertThat(doc.directoryPath()).isEqualTo(dir.path());
+    assertThat(doc.filePath()).isEqualTo(file.path());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+    assertThat(doc.language()).isEqualTo(issue.getLanguage());
+    assertThat(doc.line()).isEqualTo(issue.getLine());
+    // functional date
+    assertThat(doc.updateDate()).isEqualToIgnoringMillis(new Date(issue.getIssueUpdateTime()));
+    assertThat(doc.getCwe()).containsExactlyInAnyOrder(SecurityStandards.UNKNOWN_STANDARD);
+    assertThat(doc.getOwaspTop10()).isEmpty();
+    assertThat(doc.getSansTop25()).isEmpty();
+    assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS);
+    assertThat(doc.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);
+  }
+
+  @Test
+  public void verify_security_standards_indexation() {
+    RuleDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123", "owaspTop10:a3", "cwe:863", "owaspAsvs-4.0:2.1.1"))));
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
+    db.issues().insert(rule, project, file);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getCwe()).containsExactlyInAnyOrder("123", "863");
+    assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder("a3");
+    assertThat(doc.getOwaspAsvs40()).containsExactlyInAnyOrder("2.1.1");
+    assertThat(doc.getSansTop25()).containsExactlyInAnyOrder(SANS_TOP_25_POROUS_DEFENSES);
+  }
+
+  @Test
+  public void indexOnStartup_does_not_fail_on_errors_and_does_enable_recovery_mode() {
+    es.lockWrites(TYPE_ISSUE);
+    db.issues().insert();
+
+    Set<IndexType> uninitializedIndexTypes = emptySet();
+    assertThatThrownBy(() -> underTest.indexOnStartup(uninitializedIndexTypes))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("SYNCHRONE StartupIndexer must implement indexOnStartup");
+    assertThatIndexHasSize(0);
+    assertThatEsQueueTableHasSize(0);
+    es.unlockWrites(TYPE_ISSUE);
+  }
+
+  @Test
+  public void indexOnAnalysis_indexes_the_issues_of_project() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    IssueDto issue = db.issues().insert(rule, project, file);
+    ComponentDto otherProject = db.components().insertPrivateProject();
+    db.components().insertComponent(newFileDto(otherProject));
+
+    underTest.indexOnAnalysis(project.uuid());
+
+    assertThatIndexHasOnly(issue);
+  }
+
+  @Test
+  public void indexOnAnalysis_does_not_delete_orphan_docs() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    IssueDto issue = db.issues().insert(rule, project, file);
+
+    // orphan in the project
+    addIssueToIndex(project.uuid(), "orphan");
+
+    underTest.indexOnAnalysis(project.uuid());
+
+    assertThat(es.getDocuments(TYPE_ISSUE))
+      .extracting(SearchHit::getId)
+      .containsExactlyInAnyOrder(issue.getKey(), "orphan");
+  }
+
+  /**
+   * Indexing recovery is handled by Compute Engine, without using
+   * the table es_queue
+   */
+  @Test
+  public void indexOnAnalysis_does_not_fail_on_errors_and_does_not_enable_recovery_mode() {
+    es.lockWrites(TYPE_ISSUE);
+    IssueDto issue = db.issues().insert();
+
+    String projectUuid = issue.getProjectUuid();
+    assertThatThrownBy(() -> underTest.indexOnAnalysis(projectUuid))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
+    assertThatIndexHasSize(0);
+    assertThatEsQueueTableHasSize(0);
+    es.unlockWrites(TYPE_ISSUE);
+  }
+
+  @Test
+  public void index_is_not_updated_when_creating_project() {
+    // it's impossible to already have an issue on a project
+    // that is being created, but it's just to verify that
+    // indexing is disabled
+    IssueDto issue = db.issues().insert();
+
+    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
+    assertThat(result.getTotal()).isZero();
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void index_is_not_updated_when_updating_project_key() {
+    // issue is inserted to verify that indexing of project is not triggered
+    IssueDto issue = db.issues().insert();
+
+    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
+    assertThat(result.getTotal()).isZero();
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void index_is_not_updated_when_updating_tags() {
+    // issue is inserted to verify that indexing of project is not triggered
+    IssueDto issue = db.issues().insert();
+
+    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
+    assertThat(result.getTotal()).isZero();
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void index_is_updated_when_deleting_project() {
+    addIssueToIndex("P1", "I1");
+    assertThatIndexHasSize(1);
+
+    IndexingResult result = indexProject("P1", ProjectIndexer.Cause.PROJECT_DELETION);
+
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void errors_during_project_deletion_are_recovered() {
+    addIssueToIndex("P1", "I1");
+    assertThatIndexHasSize(1);
+    es.lockWrites(TYPE_ISSUE);
+
+    IndexingResult result = indexProject("P1", ProjectIndexer.Cause.PROJECT_DELETION);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+
+    // index is still read-only, fail to recover
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+    assertThatIndexHasSize(1);
+
+    es.unlockWrites(TYPE_ISSUE);
+
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isZero();
+    assertThatIndexHasSize(0);
+  }
+
+  @Test
+  public void commitAndIndexIssues_commits_db_transaction_and_adds_issues_to_index() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    // insert issues in db without committing
+    IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
+    IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
+    db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
+
+    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
+
+    // issues are persisted and indexed
+    assertThatIndexHasOnly(issue1, issue2);
+    assertThatDbHasOnly(issue1, issue2);
+    assertThatEsQueueTableHasSize(0);
+  }
+
+  @Test
+  public void commitAndIndexIssues_removes_issue_from_index_if_it_does_not_exist_in_db() {
+    IssueDto issue1 = new IssueDto().setKee("I1").setProjectUuid("P1");
+    addIssueToIndex(issue1.getProjectUuid(), issue1.getKey());
+    IssueDto issue2 = db.issues().insert();
+
+    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
+
+    // issue1 is removed from index, issue2 is persisted and indexed
+    assertThatIndexHasOnly(issue2);
+    assertThatDbHasOnly(issue2);
+    assertThatEsQueueTableHasSize(0);
+  }
+
+  @Test
+  public void indexing_errors_during_commitAndIndexIssues_are_recovered() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    // insert issues in db without committing
+    IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
+    IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
+    db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
+
+    // index is read-only
+    es.lockWrites(TYPE_ISSUE);
+
+    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
+
+    // issues are persisted but not indexed
+    assertThatIndexHasSize(0);
+    assertThatDbHasOnly(issue1, issue2);
+    assertThatEsQueueTableHasSize(2);
+
+    // re-enable write on index
+    es.unlockWrites(TYPE_ISSUE);
+
+    // emulate the recovery daemon
+    IndexingResult result = recover();
+
+    assertThatEsQueueTableHasSize(0);
+    assertThatIndexHasOnly(issue1, issue2);
+    assertThat(result.isSuccess()).isTrue();
+    assertThat(result.getTotal()).isEqualTo(2L);
+  }
+
+  @Test
+  public void recovery_does_not_fail_if_unsupported_docIdType() {
+    EsQueueDto item = EsQueueDto.create(TYPE_ISSUE.format(), "I1", "unknown", "P1");
+    db.getDbClient().esQueueDao().insert(db.getSession(), item);
+    db.commit();
+
+    recover();
+
+    assertThat(logTester.logs(LoggerLevel.ERROR))
+      .filteredOn(l -> l.contains("Unsupported es_queue.doc_id_type for issues. Manual fix is required: "))
+      .hasSize(1);
+    assertThatEsQueueTableHasSize(1);
+  }
+
+  @Test
+  public void indexing_recovers_multiple_errors_on_the_same_issue() {
+    es.lockWrites(TYPE_ISSUE);
+    IssueDto issue = db.issues().insert();
+
+    // three changes on the same issue
+    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
+    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
+    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
+
+    assertThatIndexHasSize(0);
+    // three attempts of indexing are stored in es_queue recovery table
+    assertThatEsQueueTableHasSize(3);
+
+    es.unlockWrites(TYPE_ISSUE);
+    recover();
+
+    assertThatIndexHasOnly(issue);
+    assertThatEsQueueTableHasSize(0);
+  }
+
+  @Test
+  public void indexing_recovers_multiple_errors_on_the_same_project() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    db.issues().insert(rule, project, file);
+    db.issues().insert(rule, project, file);
+
+    es.lockWrites(TYPE_ISSUE);
+
+    IndexingResult result = indexProject(project.uuid(), ProjectIndexer.Cause.PROJECT_DELETION);
+    assertThat(result.getTotal()).isEqualTo(2L);
+    assertThat(result.getFailures()).isEqualTo(2L);
+
+    // index is still read-only, fail to recover
+    result = recover();
+    assertThat(result.getTotal()).isEqualTo(2L);
+    assertThat(result.getFailures()).isEqualTo(2L);
+    assertThatIndexHasSize(0);
+
+    es.unlockWrites(TYPE_ISSUE);
+
+    result = recover();
+    assertThat(result.getTotal()).isEqualTo(2L);
+    assertThat(result.getFailures()).isZero();
+    assertThatIndexHasSize(2);
+    assertThatEsQueueTableHasSize(0);
+  }
+
+  private IndexingResult indexProject(String projectUuid, ProjectIndexer.Cause cause) {
+    Collection<EsQueueDto> items = underTest.prepareForRecovery(db.getSession(), singletonList(projectUuid), cause);
+    db.commit();
+    return underTest.index(db.getSession(), items);
+  }
+
+  @Test
+  public void deleteByKeys_deletes_docs_by_keys() {
+    addIssueToIndex("P1", "Issue1");
+    addIssueToIndex("P1", "Issue2");
+    addIssueToIndex("P1", "Issue3");
+    addIssueToIndex("P2", "Issue4");
+
+    assertThatIndexHasOnly("Issue1", "Issue2", "Issue3", "Issue4");
+
+    underTest.deleteByKeys("P1", asList("Issue1", "Issue2"));
+
+    assertThatIndexHasOnly("Issue3", "Issue4");
+  }
+
+  @Test
+  public void deleteByKeys_does_not_recover_from_errors() {
+    addIssueToIndex("P1", "Issue1");
+    es.lockWrites(TYPE_ISSUE);
+
+    List<String> issues = List.of("Issue1");
+    assertThatThrownBy(() -> underTest.deleteByKeys("P1", issues))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
+    assertThatIndexHasOnly("Issue1");
+    assertThatEsQueueTableHasSize(0);
+    es.unlockWrites(TYPE_ISSUE);
+  }
+
+  @Test
+  public void nothing_to_do_when_delete_issues_on_empty_list() {
+    addIssueToIndex("P1", "Issue1");
+    addIssueToIndex("P1", "Issue2");
+    addIssueToIndex("P1", "Issue3");
+
+    underTest.deleteByKeys("P1", emptyList());
+
+    assertThatIndexHasOnly("Issue1", "Issue2", "Issue3");
+  }
+
+  /**
+   * This is a technical constraint, to ensure, that the indexers can be called in any order, during startup.
+   */
+  @Test
+  public void parent_child_relationship_does_not_require_ordering_of_index_requests() {
+    IssueDoc issueDoc = new IssueDoc();
+    issueDoc.setKey("key");
+    issueDoc.setProjectUuid("parent-does-not-exist");
+    new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null)
+      .index(singletonList(issueDoc).iterator());
+
+    assertThat(es.countDocuments(TYPE_ISSUE)).isOne();
+  }
+
+  @Test
+  public void index_issue_in_non_main_branch() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java/foo"));
+    ComponentDto file = db.components().insertComponent(newFileDto(branch, dir, "F1"));
+    IssueDto issue = db.issues().insert(rule, branch, file);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
+    assertThat(doc.projectUuid()).isEqualTo(branch.getMainBranchProjectUuid());
+    assertThat(doc.branchUuid()).isEqualTo(branch.uuid());
+    assertThat(doc.isMainBranch()).isFalse();
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+  }
+
+  @Test
+  public void issue_on_test_file_has_test_scope() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1").setQualifier("UTS"));
+    IssueDto issue = db.issues().insert(rule, project, file);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.TEST);
+  }
+
+  @Test
+  public void issue_on_directory_has_main_code_scope() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
+    IssueDto issue = db.issues().insert(rule, project, dir);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.componentUuid()).isEqualTo(dir.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+  }
+
+  @Test
+  public void issue_on_project_has_main_code_scope() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    IssueDto issue = db.issues().insert(rule, project, project);
+
+    underTest.indexAllIssues();
+
+    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
+    assertThat(doc.getId()).isEqualTo(issue.getKey());
+    assertThat(doc.componentUuid()).isEqualTo(project.uuid());
+    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
+  }
+
+  @Test
+  public void getType() {
+    Assertions.assertThat(underTest.getType()).isEqualTo(StartupIndexer.Type.ASYNCHRONOUS);
+  }
+
+  @Test
+  public void indexOnAnalysis_whenChangedComponents_shouldReindexOnlyChangedComponents() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto changedComponent1 = db.components().insertComponent(newFileDto(project));
+    ComponentDto unchangedComponent = db.components().insertComponent(newFileDto(project));
+    ComponentDto ChangedComponent2 = db.components().insertComponent(newFileDto(project));
+    IssueDto changedIssue1 = db.issues().insert(rule, project, changedComponent1);
+    IssueDto changedIssue2 = db.issues().insert(rule, project, changedComponent1);
+    IssueDto changedIssue3 = db.issues().insert(rule, project, ChangedComponent2);
+    db.issues().insert(rule, project, unchangedComponent);
+    db.issues().insert(rule, project, unchangedComponent);
+
+    underTest.indexOnAnalysis(project.uuid(), Set.of(unchangedComponent.uuid()));
+
+    assertThatIndexHasOnly(changedIssue1, changedIssue2, changedIssue3);
+  }
+
+  @Test
+  public void indexOnAnalysis_whenEmptyUnchangedComponents_shouldReindexEverything() {
+    RuleDto rule = db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto changedComponent = db.components().insertComponent(newFileDto(project));
+    IssueDto changedIssue1 = db.issues().insert(rule, project, changedComponent);
+    IssueDto changedIssue2 = db.issues().insert(rule, project, changedComponent);
+
+    underTest.indexOnAnalysis(project.uuid(), Set.of());
+
+    assertThatIndexHasOnly(changedIssue1, changedIssue2);
+  }
+
+  @Test
+  public void indexOnAnalysis_whenChangedComponentWithoutIssue_shouldReindexNothing() {
+    db.rules().insert();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.components().insertComponent(newFileDto(project));
+
+    underTest.indexOnAnalysis(project.uuid(), Set.of());
+
+    assertThat(es.getDocuments(TYPE_ISSUE)).isEmpty();
+  }
+
+  private void addIssueToIndex(String projectUuid, String issueKey) {
+    es.putDocuments(TYPE_ISSUE,
+      newDoc().setKey(issueKey).setProjectUuid(projectUuid));
+  }
+
+  private void assertThatIndexHasSize(long expectedSize) {
+    assertThat(es.countDocuments(TYPE_ISSUE)).isEqualTo(expectedSize);
+  }
+
+  private void assertThatIndexHasOnly(IssueDto... expectedIssues) {
+    assertThat(es.getDocuments(TYPE_ISSUE))
+      .extracting(SearchHit::getId)
+      .containsExactlyInAnyOrder(Arrays.stream(expectedIssues).map(IssueDto::getKey).toArray(String[]::new));
+  }
+
+  private void assertThatIndexHasOnly(String... expectedKeys) {
+    List<IssueDoc> issues = es.getDocuments(TYPE_ISSUE, IssueDoc.class);
+    assertThat(issues).extracting(IssueDoc::key).containsOnly(expectedKeys);
+  }
+
+  private void assertThatEsQueueTableHasSize(int expectedSize) {
+    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
+  }
+
+  private void assertThatDbHasOnly(IssueDto... expectedIssues) {
+    try (DbSession otherSession = db.getDbClient().openSession(false)) {
+      List<String> keys = Arrays.stream(expectedIssues).map(IssueDto::getKey).collect(Collectors.toList());
+      assertThat(db.getDbClient().issueDao().selectByKeys(otherSession, keys)).hasSize(expectedIssues.length);
+    }
+  }
+
+  private IndexingResult recover() {
+    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
+    return underTest.index(db.getSession(), items);
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/index/IssueIteratorFactoryIT.java
new file mode 100644 (file)
index 0000000..594b876
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.index;
+
+import com.google.common.collect.Maps;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
+
+public class IssueIteratorFactoryIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Test
+  public void iterator_over_one_issue() {
+    RuleDto rule = dbTester.rules().insert();
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project)
+      .setPath("src/main/java/Action.java"));
+    IssueDto expected = dbTester.issues().insert(rule, project, file,
+      t -> t.setResolution("FIXED")
+        .setStatus("RESOLVED")
+        .setSeverity("BLOCKER")
+        .setManualSeverity(false)
+        .setAssigneeUuid("uuid-of-guy1")
+        .setAuthorLogin("guy2")
+        .setChecksum("FFFFF")
+        .setGap(2D)
+        .setEffort(10L)
+        .setMessage(null)
+        .setLine(444)
+        .setRuleUuid(rule.getUuid())
+        .setTags(List.of("tag1", "tag2", "tag3"))
+        .setCreatedAt(1400000000000L)
+        .setUpdatedAt(1400000000000L)
+        .setIssueCreationDate(new Date(1115848800000L))
+        .setIssueUpdateDate(new Date(1356994800000L))
+        .setIssueCloseDate(null)
+        .setType(2));
+
+    Map<String, IssueDoc> issuesByKey = issuesByKey();
+
+    assertThat(issuesByKey).hasSize(1);
+
+    IssueDoc issue = issuesByKey.get(expected.getKey());
+    assertThat(issue.key()).isEqualTo(expected.getKey());
+    assertThat(issue.resolution()).isEqualTo("FIXED");
+    assertThat(issue.status()).isEqualTo("RESOLVED");
+    assertThat(issue.severity()).isEqualTo("BLOCKER");
+    assertThat(issue.assigneeUuid()).isEqualTo("uuid-of-guy1");
+    assertThat(issue.authorLogin()).isEqualTo("guy2");
+    assertThat(issue.line()).isEqualTo(444);
+    assertThat(issue.ruleUuid()).isEqualTo(rule.getUuid());
+    assertThat(issue.componentUuid()).isEqualTo(file.uuid());
+    assertThat(issue.projectUuid()).isEqualTo(file.branchUuid());
+    assertThat(issue.directoryPath()).isEqualTo("src/main/java");
+    assertThat(issue.filePath()).isEqualTo("src/main/java/Action.java");
+    assertThat(issue.getTags()).containsOnly("tag1", "tag2", "tag3");
+    assertThat(issue.effort().toMinutes()).isPositive();
+    assertThat(issue.type().getDbConstant()).isEqualTo(2);
+  }
+
+  @Test
+  public void iterator_over_issues() {
+    RuleDto rule = dbTester.rules().insert();
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, "src/main/java"));
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(directory, directory)
+      .setPath("src/main/java/Action.java"));
+    IssueDto fileIssue = dbTester.issues().insert(rule, project, file,
+      t -> t
+        .setAssigneeUuid("uuid-of-guy1")
+        .setAuthorLogin("guy2")
+        .setEffort(10L)
+        .setType(1));
+    IssueDto moduleIssue = dbTester.issues().insert(rule, project, file, t -> t
+      .setAssigneeUuid("uuid-of-guy2")
+      .setAuthorLogin("guy2")
+      .setRuleUuid(rule.getUuid()));
+    IssueDto dirIssue = dbTester.issues().insert(rule, project, directory);
+    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
+
+    dbTester.issues().insertNewCodeReferenceIssue(newCodeReferenceIssue(fileIssue));
+
+    Map<String, IssueDoc> issuesByKey = issuesByKey();
+
+    assertThat(issuesByKey)
+      .hasSize(4)
+      .containsOnlyKeys(fileIssue.getKey(), moduleIssue.getKey(), dirIssue.getKey(), projectIssue.getKey());
+
+    assertThat(issuesByKey.get(fileIssue.getKey()).isNewCodeReference()).isTrue();
+  }
+
+  @Test
+  public void iterator_over_issue_from_project() {
+    RuleDto rule = dbTester.rules().insert();
+    ComponentDto project1 = dbTester.components().insertPrivateProject();
+    ComponentDto dir = dbTester.components().insertComponent(newDirectory(project1, "path"));
+    ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project1, dir));
+    String[] project1IssueKeys = Stream.of(project1, dir, file1)
+      .map(project1Component -> dbTester.issues().insert(rule, project1, project1Component).getKey())
+      .toArray(String[]::new);
+    ComponentDto project2 = dbTester.components().insertPrivateProject();
+    ComponentDto dir2 = dbTester.components().insertComponent(newDirectory(project2, "path"));
+    ComponentDto file2 = dbTester.components().insertComponent(newFileDto(project2, dir2));
+    String[] project2IssueKeys = Stream.of(project2, dir2, file2)
+      .map(project2Component -> dbTester.issues().insert(rule, project2, project2Component).getKey())
+      .toArray(String[]::new);
+
+    assertThat(issuesByKey(factory -> factory.createForBranch(project1.uuid())))
+      .containsOnlyKeys(project1IssueKeys);
+    assertThat(issuesByKey(factory -> factory.createForBranch(project2.uuid())))
+      .containsOnlyKeys(project2IssueKeys);
+    assertThat(issuesByKey(factory -> factory.createForBranch("does not exist")))
+      .isEmpty();
+  }
+
+  @Test
+  public void extract_directory_path() {
+    RuleDto rule = dbTester.rules().insert();
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto fileInRootDir = dbTester.components().insertComponent(newFileDto(project).setPath("pom.xml"));
+    ComponentDto fileInSubDir = dbTester.components().insertComponent(newFileDto(project).setPath("src/main/java/Action.java"));
+    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
+    IssueDto fileInSubDirIssue = dbTester.issues().insert(rule, project, fileInSubDir);
+    IssueDto fileInRootDirIssue = dbTester.issues().insert(rule, project, fileInRootDir);
+
+    Map<String, IssueDoc> issuesByKey = issuesByKey();
+
+    assertThat(issuesByKey).hasSize(3);
+    assertThat(issuesByKey.get(fileInSubDirIssue.getKey()).directoryPath()).isEqualTo("src/main/java");
+    assertThat(issuesByKey.get(fileInRootDirIssue.getKey()).directoryPath()).isEqualTo("/");
+    assertThat(issuesByKey.get(projectIssue.getKey()).directoryPath()).isNull();
+  }
+
+  @Test
+  public void extract_file_path() {
+    RuleDto rule = dbTester.rules().insert();
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto fileInRootDir = dbTester.components().insertComponent(newFileDto(project).setPath("pom.xml"));
+    ComponentDto fileInSubDir = dbTester.components().insertComponent(newFileDto(project).setPath("src/main/java/Action.java"));
+    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
+    IssueDto fileInSubDirIssue = dbTester.issues().insert(rule, project, fileInSubDir);
+    IssueDto fileInRootDirIssue = dbTester.issues().insert(rule, project, fileInRootDir);
+
+    Map<String, IssueDoc> issuesByKey = issuesByKey();
+
+    assertThat(issuesByKey).hasSize(3);
+    assertThat(issuesByKey.get(fileInSubDirIssue.getKey()).filePath()).isEqualTo("src/main/java/Action.java");
+    assertThat(issuesByKey.get(fileInRootDirIssue.getKey()).filePath()).isEqualTo("pom.xml");
+    assertThat(issuesByKey.get(projectIssue.getKey()).filePath()).isNull();
+  }
+
+  private Map<String, IssueDoc> issuesByKey() {
+    return issuesByKey(IssueIteratorFactory::createForAll);
+  }
+
+  private Map<String, IssueDoc> issuesByKey(Function<IssueIteratorFactory, IssueIterator> function) {
+    try (IssueIterator it = function.apply(new IssueIteratorFactory(dbTester.getDbClient()))) {
+      return Maps.uniqueIndex(it, IssueDoc::key);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/issue/notification/NewIssuesNotificationIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/issue/notification/NewIssuesNotificationIT.java
new file mode 100644 (file)
index 0000000..4cba1b8
--- /dev/null
@@ -0,0 +1,469 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.notification;
+
+import java.util.Date;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.Duration;
+import org.sonar.api.utils.Durations;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.notification.NewIssuesNotification.DetailsSupplier;
+import org.sonar.server.issue.notification.NewIssuesNotification.RuleDefinition;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE_TYPE;
+import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
+
+public class NewIssuesNotificationIT {
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private DetailsSupplier detailsSupplier = mock(DetailsSupplier.class);
+  private NewIssuesNotification underTest = new NewIssuesNotification(new Durations(), detailsSupplier);
+
+  @Test
+  public void set_project_without_branch() {
+    underTest.setProject("project-key", "project-long-name", null, null);
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_BRANCH)).isNull();
+  }
+
+  @Test
+  public void set_project_with_branch() {
+    underTest.setProject("project-key", "project-long-name", "feature", null);
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_BRANCH)).isEqualTo("feature");
+  }
+
+  @Test
+  public void set_project_with_pull_request() {
+    underTest.setProject("project-key", "project-long-name", null, "pr-123");
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PULL_REQUEST)).isEqualTo("pr-123");
+  }
+
+  @Test
+  public void set_project_version() {
+    String version = randomAlphanumeric(5);
+
+    underTest.setProjectVersion(version);
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isEqualTo(version);
+  }
+
+  @Test
+  public void set_project_version_supports_null() {
+    underTest.setProjectVersion(null);
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isNull();
+  }
+
+  @Test
+  public void getProjectKey_returns_null_if_setProject_has_no_been_called() {
+    assertThat(underTest.getProjectKey()).isNull();
+  }
+
+  @Test
+  public void getProjectKey_returns_projectKey_if_setProject_has_been_called() {
+    String projectKey = randomAlphabetic(5);
+    String projectName = randomAlphabetic(6);
+    String branchName = randomAlphabetic(7);
+    String pullRequest = randomAlphabetic(8);
+    underTest.setProject(projectKey, projectName, branchName, pullRequest);
+
+    assertThat(underTest.getProjectKey()).isEqualTo(projectKey);
+  }
+
+  @Test
+  public void getProjectKey_returns_value_of_field_projectKey() {
+    String projectKey = randomAlphabetic(5);
+    underTest.setFieldValue("projectKey", projectKey);
+
+    assertThat(underTest.getProjectKey()).isEqualTo(projectKey);
+  }
+
+  @Test
+  public void set_date() {
+    Date date = new Date();
+
+    underTest.setAnalysisDate(date);
+
+    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_DATE)).isEqualTo(DateUtils.formatDateTime(date));
+  }
+
+  @Test
+  public void set_statistics() {
+    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
+    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
+    ComponentDto file = db.components().insertComponent(newFileDto(directory));
+    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
+    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
+    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
+    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
+
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
+    mockDetailsSupplierComponents(project, directory, file);
+    mockDetailsSupplierRules(rule1, rule2);
+    mockDetailsSupplierAssignees(maynard, keenan);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(maynard.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo(keenan.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(TAG + ".1.label")).isEqualTo("owasp");
+    assertThat(underTest.getFieldValue(TAG + ".1.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(TAG + ".2.label")).isEqualTo("bug");
+    assertThat(underTest.getFieldValue(TAG + ".2.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(directory.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo("Rule the World (Java)");
+    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo("Rule the Universe (Clojure)");
+    assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("3");
+    assertThat(underTest.getDefaultMessage()).startsWith("8 new issues on " + project.longName());
+  }
+
+  @Test
+  public void set_statistics_when_no_issues_created_on_current_analysis() {
+    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
+    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
+    ComponentDto file = db.components().insertComponent(newFileDto(directory));
+    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
+    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
+    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
+    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
+
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> false);
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
+    mockDetailsSupplierComponents(project, directory, file);
+    mockDetailsSupplierRules(rule1, rule2);
+    mockDetailsSupplierAssignees(maynard, keenan);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("0");
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("0");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isNull();
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isNull();
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".1.label")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".1.count")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".1.label")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".1.count")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".2.count")).isNull();
+    assertThat(underTest.getDefaultMessage()).startsWith("0 new issues on " + project.longName());
+  }
+
+  @Test
+  public void set_statistics_when_some_issues_are_no_created_on_current_analysis() {
+
+    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
+    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
+    ComponentDto file = db.components().insertComponent(newFileDto(directory));
+    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
+    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
+    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
+    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
+
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> i.key().equals(issue2.getKey()));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
+    mockDetailsSupplierComponents(project, directory, file);
+    mockDetailsSupplierRules(rule1, rule2);
+    mockDetailsSupplierAssignees(maynard, keenan);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("0");
+    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(keenan.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".1.label")).isEqualTo("owasp");
+    assertThat(underTest.getFieldValue(TAG + ".1.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(TAG + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(TAG + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(directory.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo("Rule the Universe (Clojure)");
+    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("3");
+    assertThat(underTest.getFieldValue(RULE + ".2.label")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".2.count")).isNull();
+    assertThat(underTest.getDefaultMessage()).startsWith("3 new issues on " + project.longName());
+  }
+
+  private void mockDetailsSupplierAssignees(UserDto... users) {
+    for (UserDto user : users) {
+      when(detailsSupplier.getUserNameByUuid(user.getUuid())).thenReturn(Optional.of(user.getName()));
+    }
+  }
+
+  private void mockDetailsSupplierRules(RuleDto... rules) {
+    for (RuleDto rule : rules) {
+      when(detailsSupplier.getRuleDefinitionByRuleKey(rule.getKey()))
+        .thenReturn(Optional.of(new RuleDefinition(rule.getName(), rule.getLanguage())));
+    }
+  }
+
+  private void mockDetailsSupplierComponents(ComponentDto... components) {
+    for (ComponentDto component : components) {
+      when(detailsSupplier.getComponentNameByUuid(component.uuid())).thenReturn(Optional.of(component.name()));
+    }
+  }
+
+  @Test
+  public void set_assignee() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    UserDto user = db.users().insertUser();
+    IssueDto issue = db.issues().insert(rule, project, file, i -> i.setAssigneeUuid(user.getUuid()));
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue.toDefaultIssue()));
+    mockDetailsSupplierRules(rule);
+    mockDetailsSupplierAssignees(user);
+    mockDetailsSupplierComponents(project, file);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(user.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
+  }
+
+  @Test
+  public void add_only_5_assignees_with_biggest_issue_counts() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    UserDto user4 = db.users().insertUser();
+    UserDto user5 = db.users().insertUser();
+    UserDto user6 = db.users().insertUser();
+    UserDto user7 = db.users().insertUser();
+    UserDto user8 = db.users().insertUser();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user1.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user2.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user3.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user4.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user5.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user6.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user7.getUuid())).toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user8.getUuid())).toDefaultIssue()));
+    mockDetailsSupplierAssignees(user1, user2, user3, user4, user5, user6, user7, user8);
+    mockDetailsSupplierComponents(project, file);
+    mockDetailsSupplierRules(rule);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(user1.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo(user2.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.label")).isEqualTo(user3.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.label")).isEqualTo(user4.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.label")).isEqualTo(user5.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.count")).isEqualTo("6");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".6.label")).isNull();
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".6.count")).isNull();
+  }
+
+  @Test
+  public void add_only_5_components_with_biggest_issue_counts() {
+    ComponentDto project = db.components().insertPrivateProject();
+    RuleDto rule = db.rules().insert();
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    ComponentDto file1 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file1).toDefaultIssue()));
+    ComponentDto file2 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file2).toDefaultIssue()));
+    ComponentDto file3 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file3).toDefaultIssue()));
+    ComponentDto file4 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file4).toDefaultIssue()));
+    ComponentDto file5 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file5).toDefaultIssue()));
+    ComponentDto file6 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file6).toDefaultIssue()));
+    ComponentDto file7 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file7).toDefaultIssue()));
+    ComponentDto file8 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file8).toDefaultIssue()));
+    mockDetailsSupplierComponents(project, file1, file2, file3, file4, file5, file6, file7, file8);
+    mockDetailsSupplierRules(rule);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file1.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(file2.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(COMPONENT + ".3.label")).isEqualTo(file3.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(COMPONENT + ".4.label")).isEqualTo(file4.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(COMPONENT + ".5.label")).isEqualTo(file5.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".5.count")).isEqualTo("6");
+    assertThat(underTest.getFieldValue(COMPONENT + ".6.label")).isNull();
+    assertThat(underTest.getFieldValue(COMPONENT + ".6.count")).isNull();
+  }
+
+  @Test
+  public void add_only_5_rules_with_biggest_issue_counts() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    RuleDto rule1 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule1, project, file).toDefaultIssue()));
+    RuleDto rule2 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule2, project, file).toDefaultIssue()));
+    RuleDto rule3 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule3, project, file).toDefaultIssue()));
+    RuleDto rule4 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule4, project, file).toDefaultIssue()));
+    RuleDto rule5 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule5, project, file).toDefaultIssue()));
+    RuleDto rule6 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule6, project, file).toDefaultIssue()));
+    RuleDto rule7 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule7, project, file).toDefaultIssue()));
+    RuleDto rule8 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule8, project, file).toDefaultIssue()));
+    mockDetailsSupplierComponents(project, file);
+    mockDetailsSupplierRules(rule1, rule2, rule3, rule4, rule5, rule6, rule7, rule8);
+
+    underTest.setStatistics(project.longName(), stats);
+
+    String javaSuffix = " (Java)";
+    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo(rule1.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo(rule2.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(RULE + ".3.label")).isEqualTo(rule3.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(RULE + ".4.label")).isEqualTo(rule4.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(RULE + ".5.label")).isEqualTo(rule5.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".5.count")).isEqualTo("6");
+    assertThat(underTest.getFieldValue(RULE + ".6.label")).isNull();
+    assertThat(underTest.getFieldValue(RULE + ".6.count")).isNull();
+  }
+
+  @Test
+  public void set_debt() {
+    underTest.setDebt(Duration.create(55));
+
+    assertThat(underTest.getFieldValue(EFFORT + ".count")).isEqualTo("55min");
+  }
+
+  @Test
+  public void RuleDefinition_implements_equals_base_on_name_and_language() {
+    String name = randomAlphabetic(5);
+    String language = randomAlphabetic(6);
+    RuleDefinition underTest = new RuleDefinition(name, language);
+
+    assertThat(underTest)
+      .isEqualTo(underTest)
+      .isEqualTo(new RuleDefinition(name, language))
+      .isNotEqualTo(new RuleDefinition(language, name))
+      .isNotEqualTo(new RuleDefinition(randomAlphabetic(7), name))
+      .isNotEqualTo(new RuleDefinition(language, randomAlphabetic(7)))
+      .isNotEqualTo(new RuleDefinition(language, null))
+      .isNotNull()
+      .isNotEqualTo(new Object());
+  }
+
+  @Test
+  public void RuleDefinition_implements_hashcode_base_on_name_and_language() {
+    String name = randomAlphabetic(5);
+    String language = randomAlphabetic(6);
+    RuleDefinition underTest = new RuleDefinition(name, language);
+
+    assertThat(underTest)
+      .hasSameHashCodeAs(underTest)
+      .hasSameHashCodeAs(new RuleDefinition(name, language));
+
+    assertThat(underTest.hashCode())
+      .isNotEqualTo(new RuleDefinition(language, name).hashCode())
+      .isNotEqualTo(new RuleDefinition(randomAlphabetic(7), name).hashCode())
+      .isNotEqualTo(new RuleDefinition(language, randomAlphabetic(7)).hashCode())
+      .isNotEqualTo(new RuleDefinition(language, null).hashCode())
+      .isNotEqualTo(new Object().hashCode());
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/measure/index/ProjectMeasuresIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/measure/index/ProjectMeasuresIndexerIT.java
new file mode 100644 (file)
index 0000000..541cc03
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.measure.index;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.IndexingResult;
+import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.permission.index.AuthorizationScope;
+import org.sonar.server.permission.index.IndexPermissions;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.server.es.EsClient.prepareSearch;
+import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_KEY_UPDATE;
+import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALIFIER;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_UUID;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
+
+public class ProjectMeasuresIndexerIT {
+
+  private final System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private final ProjectMeasuresIndexer underTest = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
+
+  @Test
+  public void test_getAuthorizationScope() {
+    AuthorizationScope scope = underTest.getAuthorizationScope();
+    assertThat(scope.getIndexType().getIndex()).isEqualTo(ProjectMeasuresIndexDefinition.DESCRIPTOR);
+    assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
+
+    Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
+    IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
+    IndexPermissions app = new IndexPermissions("P1", Qualifiers.APP);
+    IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
+    assertThat(projectPredicate.test(project)).isTrue();
+    assertThat(projectPredicate.test(app)).isTrue();
+    assertThat(projectPredicate.test(file)).isFalse();
+  }
+
+  @Test
+  public void index_nothing() {
+    underTest.indexOnStartup(emptySet());
+
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
+  }
+
+  @Test
+  public void indexOnStartup_indexes_all_projects() {
+    SnapshotDto project1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+    SnapshotDto project2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+    SnapshotDto project3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project1, project2, project3);
+    assertThatQualifierIs("TRK", project1, project2, project3);
+  }
+
+  @Test
+  public void indexAll_indexes_all_projects() {
+    SnapshotDto project1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+    SnapshotDto project2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+    SnapshotDto project3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+
+    underTest.indexAll();
+
+    assertThatIndexContainsOnly(project1, project2, project3);
+    assertThatQualifierIs("TRK", project1, project2, project3);
+  }
+
+  /**
+   * Provisioned projects don't have analysis yet
+   */
+  @Test
+  public void indexOnStartup_indexes_provisioned_projects() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void indexOnStartup_ignores_non_main_branches() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void indexOnStartup_indexes_all_applications() {
+    ComponentDto application1 = db.components().insertPrivateApplication();
+    ComponentDto application2 = db.components().insertPrivateApplication();
+    ComponentDto application3 = db.components().insertPrivateApplication();
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(application1, application2, application3);
+    assertThatQualifierIs("APP", application1, application2, application3);
+  }
+
+  @Test
+  public void indexOnStartup_indexes_projects_and_applications() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    ComponentDto project3 = db.components().insertPrivateProject();
+
+    ComponentDto application1 = db.components().insertPrivateApplication();
+    ComponentDto application2 = db.components().insertPrivateApplication();
+    ComponentDto application3 = db.components().insertPrivateApplication();
+
+    underTest.indexOnStartup(emptySet());
+
+    assertThatIndexContainsOnly(project1, project2, project3, application1, application2, application3);
+    assertThatQualifierIs("TRK", project1, project2, project3);
+    assertThatQualifierIs("APP", application1, application2, application3);
+  }
+
+  @Test
+  public void indexOnAnalysis_indexes_provisioned_project() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    underTest.indexOnAnalysis(project1.uuid());
+
+    assertThatIndexContainsOnly(project1);
+  }
+
+  @Test
+  public void indexOnAnalysis_indexes_provisioned_application() {
+    ComponentDto app1 = db.components().insertPrivateApplication();
+    ComponentDto app2 = db.components().insertPrivateApplication();
+
+    underTest.indexOnAnalysis(app1.uuid());
+
+    assertThatIndexContainsOnly(app1);
+  }
+
+  @Test
+  public void update_index_when_project_key_is_updated() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    IndexingResult result = indexProject(project, PROJECT_KEY_UPDATE);
+
+    assertThatIndexContainsOnly(project);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+  }
+
+  @Test
+  public void update_index_when_project_is_created() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    IndexingResult result = indexProject(project, PROJECT_CREATION);
+
+    assertThatIndexContainsOnly(project);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+  }
+
+  @Test
+  public void update_index_when_project_tags_are_updated() {
+    ComponentDto project = db.components().insertPrivateProject(defaults(), p -> p.setTagsString("foo"));
+    indexProject(project, PROJECT_CREATION);
+    assertThatProjectHasTag(project, "foo");
+
+    ProjectDto projectDto = db.components().getProjectDto(project);
+    projectDto.setTagsString("bar");
+    db.getDbClient().projectDao().updateTags(db.getSession(), projectDto);
+    // TODO change indexing?
+    IndexingResult result = indexProject(project, PROJECT_TAGS_UPDATE);
+
+    assertThatProjectHasTag(project, "bar");
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+  }
+
+  @Test
+  public void delete_doc_from_index_when_project_is_deleted() {
+    ComponentDto project = db.components().insertPrivateProject();
+    indexProject(project, PROJECT_CREATION);
+    assertThatIndexContainsOnly(project);
+
+    db.getDbClient().purgeDao().deleteProject(db.getSession(), project.uuid(), Qualifiers.PROJECT, project.name(), project.getKey());
+    IndexingResult result = indexProject(project, PROJECT_DELETION);
+
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getSuccess()).isOne();
+  }
+
+  @Test
+  public void do_nothing_if_no_projects_and_apps_to_index() {
+    // this project should not be indexed
+    db.components().insertPrivateProject();
+    db.components().insertPrivateApplication();
+
+    underTest.index(db.getSession(), emptyList());
+
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
+  }
+
+  @Test
+  public void errors_during_indexing_are_recovered() {
+    ComponentDto project = db.components().insertPrivateProject();
+    es.lockWrites(TYPE_PROJECT_MEASURES);
+
+    IndexingResult result = indexProject(project, PROJECT_CREATION);
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+
+    // index is still read-only, fail to recover
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isOne();
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
+    assertThatEsQueueTableHasSize(1);
+
+    es.unlockWrites(TYPE_PROJECT_MEASURES);
+
+    result = recover();
+    assertThat(result.getTotal()).isOne();
+    assertThat(result.getFailures()).isZero();
+    assertThatEsQueueTableHasSize(0);
+    assertThatIndexContainsOnly(project);
+  }
+
+  @Test
+  public void non_main_branches_are_not_indexed_during_analysis() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
+
+    underTest.indexOnAnalysis(branch.uuid());
+
+    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
+  }
+
+  private IndexingResult indexProject(ComponentDto project, ProjectIndexer.Cause cause) {
+    DbSession dbSession = db.getSession();
+    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
+    dbSession.commit();
+    return underTest.index(dbSession, items);
+  }
+
+  private void assertThatProjectHasTag(ComponentDto project, String expectedTag) {
+    SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
+      .source(new SearchSourceBuilder()
+        .query(boolQuery()
+          .filter(termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()))
+          .filter(termQuery(FIELD_TAGS, expectedTag))));
+
+    assertThat(es.client().search(request).getHits().getHits())
+      .extracting(SearchHit::getId)
+      .contains(project.uuid());
+  }
+
+  private void assertThatEsQueueTableHasSize(int expectedSize) {
+    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
+  }
+
+  private void assertThatIndexContainsOnly(SnapshotDto... expectedProjects) {
+    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
+      Arrays.stream(expectedProjects).map(SnapshotDto::getComponentUuid).toArray(String[]::new));
+  }
+
+  private void assertThatIndexContainsOnly(ComponentDto... expectedProjects) {
+    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
+      Arrays.stream(expectedProjects).map(ComponentDto::uuid).toArray(String[]::new));
+  }
+
+  private void assertThatQualifierIs(String qualifier, ComponentDto... expectedComponents) {
+    String[] expectedComponentUuids = Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new);
+    assertThatQualifierIs(qualifier, expectedComponentUuids);
+  }
+
+  private void assertThatQualifierIs(String qualifier, SnapshotDto... expectedComponents) {
+    String[] expectedComponentUuids = Arrays.stream(expectedComponents).map(SnapshotDto::getComponentUuid).toArray(String[]::new);
+    assertThatQualifierIs(qualifier, expectedComponentUuids);
+  }
+
+  private void assertThatQualifierIs(String qualifier, String... componentsUuid) {
+    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
+      .query(boolQuery()
+        .filter(termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()))
+        .filter(termQuery(FIELD_QUALIFIER, qualifier))
+        .filter(termsQuery(FIELD_UUID, componentsUuid)));
+
+    SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
+      .source(searchSourceBuilder);
+    assertThat(es.client().search(request).getHits().getHits())
+      .extracting(SearchHit::getId)
+      .containsExactlyInAnyOrder(componentsUuid);
+  }
+
+  private IndexingResult recover() {
+    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
+    return underTest.index(db.getSession(), items);
+  }
+
+  private static <T> Consumer<T> defaults() {
+    return t -> {
+      // do nothing
+    };
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/metric/MetricFinderIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/metric/MetricFinderIT.java
new file mode 100644 (file)
index 0000000..81b5273
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.metric;
+
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.metric.MetricTesting.newMetricDto;
+
+public class MetricFinderIT {
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final MetricFinder underTest = new MetricFinder(db.getDbClient());
+
+  @Test
+  public void findAll_enabled() {
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
+    db.commit();
+
+    assertThat(underTest.findAll()).hasSize(2);
+  }
+
+  @Test
+  public void findAll_by_keys() {
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("ncloc"));
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("foo"));
+    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("coverage"));
+    db.commit();
+
+    assertThat(underTest.findAll(Arrays.asList("ncloc", "foo"))).extracting(Metric::getKey).containsExactlyInAnyOrder("ncloc", "foo")
+      .doesNotContain("coverage");
+
+  }
+
+  @Test
+  public void findById() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.commit();
+
+    assertThat(underTest.findByUuid(firstMetric.getUuid())).extracting(Metric::getKey).isEqualTo(firstMetric.getKey());
+  }
+
+  @Test
+  public void findById_filters_out_disabled() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
+    db.commit();
+
+    assertThat(underTest.findByUuid(secondMetric.getUuid())).isNull();
+  }
+
+  @Test
+  public void findById_doesnt_find_anything() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.commit();
+
+    assertThat(underTest.findByUuid("non existing")).isNull();
+  }
+
+  @Test
+  public void findByKey() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.commit();
+
+    assertThat(underTest.findByKey(secondMetric.getKey())).extracting(Metric::getKey).isEqualTo(secondMetric.getKey());
+  }
+
+  @Test
+  public void findByKey_filters_out_disabled() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
+    db.commit();
+
+    assertThat(underTest.findByKey(secondMetric.getKey())).isNull();
+  }
+
+  @Test
+  public void findByKey_doesnt_find_anything() {
+    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
+    db.commit();
+
+    assertThat(underTest.findByKey("doesnt exist")).isNull();
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/platform/StartupMetadataProviderIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/platform/StartupMetadataProviderIT.java
new file mode 100644 (file)
index 0000000..6bb87bb
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.Version;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+
+public class StartupMetadataProviderIT {
+  private static final long A_DATE = 1_500_000_000_000L;
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final StartupMetadataProvider underTest = new StartupMetadataProvider();
+  private final System2 system = mock(System2.class);
+  private final NodeInformation nodeInformation = mock(NodeInformation.class);
+
+  @Test
+  public void generate_SERVER_STARTIME_but_do_not_persist_it_if_server_is_startup_leader() {
+    when(system.now()).thenReturn(A_DATE);
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER, SonarEdition.COMMUNITY);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    StartupMetadata metadata = underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient());
+    assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
+
+    assertNotPersistedProperty(CoreProperties.SERVER_STARTTIME);
+  }
+
+  @Test
+  public void load_from_database_if_server_is_startup_follower() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER, SonarEdition.COMMUNITY);
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    testLoadingFromDatabase(runtime, false);
+  }
+
+  @Test
+  public void load_from_database_if_compute_engine_of_startup_leader_server() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
+
+    testLoadingFromDatabase(runtime, true);
+  }
+
+  @Test
+  public void load_from_database_if_compute_engine_of_startup_follower_server() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
+
+    testLoadingFromDatabase(runtime, false);
+  }
+
+  @Test
+  public void fail_to_load_from_database_if_properties_are_not_persisted() {
+    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    assertThatThrownBy(() -> underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Property sonar.core.startTime is missing in database");
+  }
+
+  private void testLoadingFromDatabase(SonarRuntime runtime, boolean isStartupLeader) {
+    dbTester.properties().insertProperty(new PropertyDto().setKey(CoreProperties.SERVER_STARTTIME).setValue(formatDateTime(A_DATE)),
+      null, null,null, null);
+    when(nodeInformation.isStartupLeader()).thenReturn(isStartupLeader);
+
+    StartupMetadata metadata = underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient());
+    assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
+
+    // still in database
+    assertPersistedProperty(CoreProperties.SERVER_STARTTIME, formatDateTime(A_DATE));
+
+    verifyNoInteractions(system);
+  }
+
+  private void assertPersistedProperty(String propertyKey, String expectedValue) {
+    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+    assertThat(prop.getValue()).isEqualTo(expectedValue);
+  }
+
+  private void assertNotPersistedProperty(String propertyKey) {
+    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+    assertThat(prop).isNull();
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/platform/monitoring/DbSectionIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/platform/monitoring/DbSectionIT.java
new file mode 100644 (file)
index 0000000..6ce9810
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class DbSectionIT {
+
+  @Rule
+  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Test
+  public void db_info() {
+    DbSection underTest = new DbSection(dbTester.getDbClient());
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    SystemInfoTesting.assertThatAttributeIs(section, "Database", "H2");
+    assertThat(attribute(section, "Database Version").getStringValue()).startsWith("2.");
+    SystemInfoTesting.assertThatAttributeIs(section, "Username", "SONAR");
+    assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("2.");
+    SystemInfoTesting.assertThatAttributeIs(section, "Default transaction isolation", "TRANSACTION_READ_COMMITTED");
+  }
+
+  @Test
+  public void db_info_unknown_transaction_isolation() throws SQLException {
+    DbClient dbClient = prepareClientWithUnknownTransactionLevel();
+
+    DbSection underTest = new DbSection(dbClient);
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    SystemInfoTesting.assertThatAttributeIs(section, "Default transaction isolation", "Unknown transaction level: 42");
+  }
+
+  private static DbClient prepareClientWithUnknownTransactionLevel() throws SQLException {
+    DbClient dbClient = mock(DbClient.class);
+    DbSession dbSession = mock(DbSession.class);
+    Connection connection = mock(Connection.class);
+    DatabaseMetaData metadata = mock(DatabaseMetaData.class);
+
+    when(dbClient.openSession(false)).thenReturn(dbSession);
+    when(dbSession.getConnection()).thenReturn(connection);
+    when(connection.getMetaData()).thenReturn(metadata);
+    when(metadata.getDefaultTransactionIsolation()).thenReturn(42);
+    return dbClient;
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/qualitygate/QualityGateFinderIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/qualitygate/QualityGateFinderIT.java
new file mode 100644 (file)
index 0000000..27fe578
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualitygate;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class QualityGateFinderIT {
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final DbSession dbSession = db.getSession();
+
+  private final QualityGateFinder underTest = new QualityGateFinder(db.getDbClient());
+
+  @Test
+  public void return_default_quality_gate_for_project() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
+
+    QualityGateFinder.QualityGateData result = underTest.getEffectiveQualityGate(dbSession, project);
+
+    assertThat(result.getUuid()).isEqualTo(dbQualityGate.getUuid());
+    assertThat(result.isDefault()).isTrue();
+  }
+
+  @Test
+  public void return_project_quality_gate_over_default() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
+    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
+    db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
+
+    QualityGateFinder.QualityGateData result = underTest.getEffectiveQualityGate(dbSession, project);
+
+    assertThat(result.getUuid()).isEqualTo(dbQualityGate.getUuid());
+    assertThat(result.isDefault()).isFalse();
+  }
+
+  @Test
+  public void fail_when_default_qgate_defined_does_not_exist() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
+    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+    db.commit();
+
+    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Default quality gate is missing");
+  }
+
+  @Test
+  public void fail_when_project_qgate_defined_does_not_exist() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
+    db.qualityGates().setDefaultQualityGate(dbQualityGate);
+    db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
+    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+
+    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Default quality gate is missing");
+  }
+
+  @Test
+  public void fail_when_qgate_property_does_not_exist() {
+    ProjectDto project = db.components().insertPrivateProjectDto();
+    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
+    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+
+    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Default quality gate is missing");
+  }
+
+  @Test
+  public void fail_when_default_quality_gate_does_not_exists() {
+    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
+    db.qualityGates().setDefaultQualityGate(dbQualityGate);
+    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
+
+    assertThatThrownBy(() -> underTest.getDefault(dbSession))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Default quality gate is missing");
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerIT.java
new file mode 100644 (file)
index 0000000..60ffc0f
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.qualityprofile.index;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.assertj.core.groups.Tuple;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.es.EsQueueDto;
+import org.sonar.db.qualityprofile.ActiveRuleDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.qualityprofile.ActiveRuleChange;
+
+import static java.util.Arrays.stream;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
+
+public class ActiveRuleIndexerIT {
+
+  private System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private ActiveRuleIndexer underTest = new ActiveRuleIndexer(db.getDbClient(), es.client());
+  private RuleDto rule1;
+  private RuleDto rule2;
+  private QProfileDto profile1;
+  private QProfileDto profile2;
+
+  @Before
+  public void before() {
+    rule1 = db.rules().insert();
+    rule2 = db.rules().insert();
+    profile1 = db.qualityProfiles().insert();
+    profile2 = db.qualityProfiles().insert();
+  }
+
+  @Test
+  public void getIndexTypes() {
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ACTIVE_RULE);
+  }
+
+  @Test
+  public void indexOnStartup_does_nothing_if_no_data() {
+    underTest.indexOnStartup(emptySet());
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
+  }
+
+  @Test
+  public void indexOnStartup_indexes_all_data() {
+    ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile1, rule1);
+
+    underTest.indexOnStartup(emptySet());
+
+    List<ActiveRuleDoc> docs = es.getDocuments(TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
+    assertThat(docs).hasSize(1);
+    verify(docs.get(0), profile1, activeRule);
+    assertThatEsQueueTableIsEmpty();
+  }
+
+  @Test
+  public void indexAll_indexes_all_data() {
+    ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile1, rule1);
+
+    underTest.indexAll();
+
+    List<ActiveRuleDoc> docs = es.getDocuments(TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
+    assertThat(docs).hasSize(1);
+    verify(docs.get(0), profile1, activeRule);
+    assertThatEsQueueTableIsEmpty();
+  }
+
+  @Test
+  public void test_commitAndIndex() {
+    ActiveRuleDto ar1 = db.qualityProfiles().activateRule(profile1, rule1);
+    ActiveRuleDto ar2 = db.qualityProfiles().activateRule(profile2, rule1);
+    db.qualityProfiles().activateRule(profile2, rule2);
+
+    commitAndIndex(rule1, ar1, ar2);
+
+    verifyOnlyIndexed(ar1, ar2);
+    assertThatEsQueueTableIsEmpty();
+  }
+
+  @Test
+  public void commitAndIndex_empty_list() {
+    db.qualityProfiles().activateRule(profile1, rule1);
+
+    underTest.commitAndIndex(db.getSession(), Collections.emptyList());
+
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
+    assertThatEsQueueTableIsEmpty();
+  }
+
+  @Test
+  public void commitAndIndex_keeps_elements_to_recover_in_ES_QUEUE_on_errors() {
+    ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
+    es.lockWrites(TYPE_ACTIVE_RULE);
+
+    commitAndIndex(rule1, ar);
+
+    EsQueueDto expectedItem = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "ar_" + ar.getUuid(), "activeRuleUuid", ar.getRuleUuid());
+    assertThatEsQueueContainsExactly(expectedItem);
+    es.unlockWrites(TYPE_ACTIVE_RULE);
+  }
+
+  @Test
+  public void commitAndIndex_deletes_the_documents_that_dont_exist_in_database() {
+    ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
+    indexAll();
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
+
+    db.getDbClient().activeRuleDao().delete(db.getSession(), ar.getKey());
+    commitAndIndex(rule1, ar);
+
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
+    assertThatEsQueueTableIsEmpty();
+  }
+
+  @Test
+  public void index_fails_and_deletes_doc_if_docIdType_is_unsupported() {
+    EsQueueDto item = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "the_id", "unsupported", "the_routing");
+    db.getDbClient().esQueueDao().insert(db.getSession(), item);
+
+    underTest.index(db.getSession(), singletonList(item));
+
+    assertThatEsQueueTableIsEmpty();
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
+  }
+
+  @Test
+  public void commitDeletionOfProfiles() {
+    ActiveRuleDto ar1 = db.qualityProfiles().activateRule(profile1, rule1);
+    db.qualityProfiles().activateRule(profile2, rule1);
+    db.qualityProfiles().activateRule(profile2, rule2);
+    indexAll();
+    db.getDbClient().qualityProfileDao().deleteRulesProfilesByUuids(db.getSession(), singletonList(profile2.getRulesProfileUuid()));
+
+    underTest.commitDeletionOfProfiles(db.getSession(), singletonList(profile2));
+
+    verifyOnlyIndexed(ar1);
+  }
+
+  @Test
+  public void commitDeletionOfProfiles_does_nothing_if_profiles_are_not_indexed() {
+    db.qualityProfiles().activateRule(profile1, rule1);
+    indexAll();
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
+
+    underTest.commitDeletionOfProfiles(db.getSession(), singletonList(profile2));
+
+    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
+  }
+
+  private void assertThatEsQueueTableIsEmpty() {
+    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isZero();
+  }
+
+  private void assertThatEsQueueContainsExactly(EsQueueDto expected) {
+    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), system2.now() + 1_000, 10);
+    assertThat(items)
+      .extracting(EsQueueDto::getDocId, EsQueueDto::getDocIdType, EsQueueDto::getDocRouting)
+      .containsExactlyInAnyOrder(Tuple.tuple(expected.getDocId(), expected.getDocIdType(), expected.getDocRouting()));
+  }
+
+  private void commitAndIndex(RuleDto rule, ActiveRuleDto... ar) {
+    underTest.commitAndIndex(db.getSession(), stream(ar)
+      .map(a -> new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, a, rule))
+      .collect(Collectors.toList()));
+  }
+
+  private void verifyOnlyIndexed(ActiveRuleDto... expected) {
+    List<String> docs = es.getIds(TYPE_ACTIVE_RULE);
+    assertThat(docs).hasSize(expected.length);
+    for (ActiveRuleDto activeRuleDto : expected) {
+      assertThat(docs).contains("ar_" + activeRuleDto.getUuid());
+    }
+  }
+
+  private void verify(ActiveRuleDoc doc1, QProfileDto profile, ActiveRuleDto activeRule) {
+    assertThat(doc1)
+      .matches(doc -> doc.getId().equals("ar_" + activeRule.getUuid()))
+      .matches(doc -> doc.getRuleProfileUuid().equals(profile.getRulesProfileUuid()))
+      .matches(doc -> doc.getSeverity().equals(activeRule.getSeverityString()));
+  }
+
+  private void indexAll() {
+    underTest.indexOnStartup(emptySet());
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/DefaultRuleFinderIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/DefaultRuleFinderIT.java
new file mode 100644 (file)
index 0000000..619ffd8
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule;
+
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleDto.Scope;
+
+import static java.util.Collections.emptySet;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class DefaultRuleFinderIT {
+
+  @org.junit.Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession session = dbTester.getSession();
+
+  private final RuleDto rule1 = new RuleDto()
+    .setName("Check Header")
+    .setConfigKey("Checker/Treewalker/HeaderCheck")
+    .setRuleKey("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck")
+    .setRepositoryKey("checkstyle")
+    .setSeverity(4)
+    .setScope(Scope.MAIN)
+    .setStatus(RuleStatus.READY);
+
+  private final RuleDto rule2 = new RuleDto()
+    .setName("Disabled checked")
+    .setConfigKey("Checker/Treewalker/DisabledCheck")
+    .setRuleKey("DisabledCheck")
+    .setRepositoryKey("checkstyle")
+    .setSeverity(4)
+    .setScope(Scope.MAIN)
+    .setStatus(RuleStatus.REMOVED);
+
+  private final RuleDto rule3 = new RuleDto()
+    .setName("Check Annotation")
+    .setConfigKey("Checker/Treewalker/AnnotationUseStyleCheck")
+    .setRuleKey("com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck")
+    .setRepositoryKey("checkstyle")
+    .setSeverity(4)
+    .setScope(Scope.MAIN)
+    .setStatus(RuleStatus.READY);
+
+  private final RuleDto rule4 = new RuleDto()
+    .setName("Call Super First")
+    .setConfigKey("rulesets/android.xml/CallSuperFirst")
+    .setRuleKey("CallSuperFirst")
+    .setRepositoryKey("pmd")
+    .setSeverity(2)
+    .setScope(Scope.MAIN)
+    .setStatus(RuleStatus.READY);
+
+  private final DefaultRuleFinder underTest = new DefaultRuleFinder(dbClient, mock(RuleDescriptionFormatter.class));
+
+  @Before
+  public void setup() {
+    dbTester.rules().insert(rule1);
+    dbTester.rules().insert(rule2);
+    dbTester.rules().insert(rule3);
+    dbTester.rules().insert(rule4);
+    session.commit();
+  }
+
+  @Test
+  public void should_success_finder_wrap() {
+    // should_find_by_key
+    Rule rule = underTest.findByKey("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck");
+    assertThat(rule).isNotNull();
+    assertThat(rule.getKey()).isEqualTo(("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"));
+    assertThat(rule.isEnabled()).isTrue();
+
+    // find_should_return_null_if_no_results
+    assertThat(underTest.findByKey("checkstyle", "unknown")).isNull();
+    assertThat(underTest.find(RuleQuery.create().withRepositoryKey("checkstyle").withConfigKey("unknown"))).isNull();
+
+    // find_repository_rules
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey("checkstyle"))).hasSize(2);
+
+    // find_all_enabled
+    assertThat(underTest.findAll(RuleQuery.create())).extracting(Rule::ruleKey).containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
+
+    // find_all
+    assertThat(underTest.findAll()).extracting(RuleDto::getRuleKey).containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
+
+    // do_not_find_disabled_rules
+    assertThat(underTest.findByKey("checkstyle", "DisabledCheck")).isNull();
+
+    // do_not_find_unknown_rules
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey("unknown_repository"))).isEmpty();
+
+    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "CallSuperFirst")).get().getUuid()).isEqualTo(rule4.getUuid());
+    assertThat(underTest.findDtoByUuid(rule4.getUuid())).isPresent();
+  }
+
+  @Test
+  public void should_fail_find() {
+    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "unknown"))).isEmpty();
+    assertThat(underTest.findDtoByUuid("unknown")).isEmpty();
+  }
+
+  @Test
+  public void find_all_not_include_removed_rule() {
+    // rule 3 is REMOVED
+    assertThat(underTest.findAll(RuleQuery.create())).extracting(Rule::ruleKey).containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
+    assertThat(underTest.findAll()).extracting(RuleDto::getRuleKey).containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
+  }
+
+  @Test
+  public void findByKey_populates_system_tags_but_not_tags() {
+    RuleDto ruleDto = dbTester.rules()
+      .insert(t -> t.setSystemTags(Set.of(randomAlphanumeric(5), randomAlphanumeric(6))).setTags(emptySet()));
+    dbTester.rules().insertRule();
+
+    Rule rule = underTest.findByKey(ruleDto.getKey());
+    assertThat(rule.getSystemTags())
+      .containsOnlyElementsOf(ruleDto.getSystemTags());
+    assertThat(rule.getTags()).isEmpty();
+
+    rule = underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey());
+    assertThat(rule.getSystemTags())
+      .containsOnlyElementsOf(ruleDto.getSystemTags());
+    assertThat(rule.getTags()).isEmpty();
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexDefinitionIT.java
new file mode 100644 (file)
index 0000000..b90fe9a
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule.index;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+import org.apache.lucene.search.TotalHits;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.indices.AnalyzeRequest;
+import org.elasticsearch.client.indices.AnalyzeResponse;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Index;
+import org.sonar.server.es.IndexDefinition;
+import org.sonar.server.es.IndexType;
+import org.sonar.server.es.newindex.NewIndex;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
+import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
+import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_REPOSITORY;
+import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_UUID;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+
+public class RuleIndexDefinitionIT {
+
+  private MapSettings settings = new MapSettings();
+  private RuleIndexDefinition underTest = new RuleIndexDefinition(settings.asConfig());
+
+  @Rule
+  public EsTester tester = EsTester.create();
+
+  @Test
+  public void test_definition_of_index() {
+    IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
+    underTest.define(context);
+
+    assertThat(context.getIndices()).hasSize(1);
+    NewIndex<?> ruleIndex = context.getIndices().get("rules");
+    assertThat(ruleIndex.getMainType())
+      .isEqualTo(IndexType.main(Index.withRelations("rules"), "rule"));
+    assertThat(ruleIndex.getRelationsStream())
+      .extracting(IndexType.IndexRelationType::getName)
+      .containsOnly("activeRule");
+
+    // no cluster by default
+    assertThat(ruleIndex.getSetting("index.number_of_shards")).isEqualTo("2");
+    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("0");
+  }
+
+  @Test
+  public void enable_replica_if_clustering_is_enabled() {
+    settings.setProperty(CLUSTER_ENABLED.getKey(), true);
+    IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
+    underTest.define(context);
+
+    NewIndex ruleIndex = context.getIndices().get("rules");
+    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("1");
+  }
+
+  @Test
+  public void support_long_html_description() {
+    String longText = StringUtils.repeat("The quick brown fox jumps over the lazy dog ", 700);
+
+    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(longText);
+    assertThat(tokens).extracting(AnalyzeResponse.AnalyzeToken::getTerm).containsOnly(
+      "quick", "brown", "fox", "jump", "over", "lazi", "dog");
+
+    // the following method fails if PUT fails
+    tester.putDocuments(TYPE_RULE, new RuleDoc(ImmutableMap.of(
+      FIELD_RULE_UUID, "123",
+      FIELD_RULE_HTML_DESCRIPTION, longText,
+      FIELD_RULE_REPOSITORY, "java",
+      FIELD_RULE_KEY, "java:S001")));
+    assertThat(tester.countDocuments(TYPE_RULE)).isOne();
+    assertThat(tester.client().search(EsClient.prepareSearch(TYPE_RULE)
+      .source(new SearchSourceBuilder()
+        .query(matchQuery(ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), "brown fox jumps lazy"))))
+      .getHits().getTotalHits()).isEqualTo(new TotalHits(1, TotalHits.Relation.EQUAL_TO));
+  }
+
+  @Test
+  public void remove_html_characters_of_html_description() {
+    String text = "<p>html <i>line</i></p>";
+    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(text);
+
+    assertThat(tokens).extracting("term").containsOnly("html", "line");
+  }
+
+  @Test
+  public void sanitize_html_description_as_it_is_english() {
+    String text = "this is a small list of words";
+    // "this", "is", "a" and "of" are not indexed.
+    // Plural "words" is converted to singular "word"
+    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(text);
+    assertThat(tokens).extracting("term").containsOnly("small", "list", "word");
+  }
+
+  private List<AnalyzeResponse.AnalyzeToken> analyzeIndexedTokens(String text) {
+    try {
+      return tester.nativeClient().indices()
+        .analyze(AnalyzeRequest.withField(TYPE_RULE.getIndex().getName(), ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), text), RequestOptions.DEFAULT)
+        .getTokens();
+    } catch (IOException e) {
+      throw new IllegalStateException("Could not analyze indexed tokens for text: " + text);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexIT.java
new file mode 100644 (file)
index 0000000..6a391e4
--- /dev/null
@@ -0,0 +1,1119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule.index;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbTester;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.Facets;
+import org.sonar.server.es.SearchIdResult;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
+import org.sonar.server.security.SecurityStandards;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
+import static java.util.stream.IntStream.rangeClosed;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+import static org.junit.Assert.fail;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rule.Severity.CRITICAL;
+import static org.sonar.api.rule.Severity.INFO;
+import static org.sonar.api.rule.Severity.MAJOR;
+import static org.sonar.api.rule.Severity.MINOR;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.api.rules.RuleType.VULNERABILITY;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
+import static org.sonar.db.rule.RuleTesting.newRule;
+import static org.sonar.db.rule.RuleTesting.setCreatedAt;
+import static org.sonar.db.rule.RuleTesting.setIsExternal;
+import static org.sonar.db.rule.RuleTesting.setIsTemplate;
+import static org.sonar.db.rule.RuleTesting.setLanguage;
+import static org.sonar.db.rule.RuleTesting.setName;
+import static org.sonar.db.rule.RuleTesting.setRepositoryKey;
+import static org.sonar.db.rule.RuleTesting.setRuleKey;
+import static org.sonar.db.rule.RuleTesting.setSecurityStandards;
+import static org.sonar.db.rule.RuleTesting.setSeverity;
+import static org.sonar.db.rule.RuleTesting.setStatus;
+import static org.sonar.db.rule.RuleTesting.setSystemTags;
+import static org.sonar.db.rule.RuleTesting.setTags;
+import static org.sonar.db.rule.RuleTesting.setTemplateId;
+import static org.sonar.db.rule.RuleTesting.setType;
+import static org.sonar.db.rule.RuleTesting.setUpdatedAt;
+import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
+import static org.sonar.server.qualityprofile.ActiveRuleInheritance.OVERRIDES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_REPOSITORIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS;
+import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
+import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
+
+public class RuleIndexIT {
+
+  private final System2 system2 = new AlwaysIncreasingSystem2();
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
+  private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client());
+
+  private final RuleIndex underTest = new RuleIndex(es.client(), system2);
+  private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
+  @Test
+  public void search_all_rules() {
+    createRule();
+    createRule();
+    index();
+
+    SearchIdResult<String> results = underTest.search(new RuleQuery(), new SearchOptions());
+
+    assertThat(results.getTotal()).isEqualTo(2);
+    assertThat(results.getUuids()).hasSize(2);
+  }
+
+  @Test
+  public void search_by_key() {
+    RuleDto js1 = createRule(
+      setRepositoryKey("javascript"),
+      setRuleKey("X001"));
+    RuleDto cobol1 = createRule(
+      setRepositoryKey("cobol"),
+      setRuleKey("X001"));
+    createRule(
+      setRepositoryKey("php"),
+      setRuleKey("S002"));
+    index();
+
+    // key
+    RuleQuery query = new RuleQuery().setQueryText("X001");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(js1.getUuid(), cobol1.getUuid());
+
+    // partial key does not match
+    query = new RuleQuery().setQueryText("X00");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // repo:key -> nice-to-have !
+    query = new RuleQuery().setQueryText("javascript:X001");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(js1.getUuid());
+  }
+
+  @Test
+  public void search_by_case_insensitive_key() {
+    RuleDto ruleDto = createRule(
+      setRepositoryKey("javascript"),
+      setRuleKey("X001"));
+    index();
+
+    RuleQuery query = new RuleQuery().setQueryText("x001");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(ruleDto.getUuid());
+  }
+
+  @Test
+  public void filter_by_key() {
+    createRule(
+      setRepositoryKey("javascript"),
+      setRuleKey("X001"));
+    createRule(
+      setRepositoryKey("cobol"),
+      setRuleKey("X001"));
+    createRule(
+      setRepositoryKey("php"),
+      setRuleKey("S002"));
+    index();
+
+    // key
+    RuleQuery query = new RuleQuery().setKey(RuleKey.of("javascript", "X001").toString());
+
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
+
+    // partial key does not match
+    query = new RuleQuery().setKey("X001");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+  }
+
+  @Test
+  public void search_name_by_query() {
+    createRule(setName("testing the partial match and matching of rule"));
+    index();
+
+    // substring
+    RuleQuery query = new RuleQuery().setQueryText("test");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
+
+    // substring
+    query = new RuleQuery().setQueryText("partial match");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
+
+    // case-insensitive
+    query = new RuleQuery().setQueryText("TESTING");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
+
+    // not found
+    query = new RuleQuery().setQueryText("not present");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+  }
+
+  @Test
+  public void search_name_with_protected_chars() {
+    RuleDto rule = createRule(setName("ja#va&sc\"r:ipt"));
+    index();
+
+    RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(rule.getName());
+    List<String> results = underTest.search(protectedCharsQuery, new SearchOptions()).getUuids();
+    assertThat(results).containsOnly(rule.getUuid());
+  }
+
+  @Test
+  public void search_content_by_query() {
+    // it's important to set all the fields being used by the search (name, desc, key, lang, ...),
+    // otherwise the generated random values may raise false-positives
+    RuleDto rule1 = insertJavaRule("My great rule CWE-123 which makes your code 1000 times better!", "123", "rule 123");
+    RuleDto rule2 = insertJavaRule("Another great and shiny rule CWE-124", "124", "rule 124");
+    RuleDto rule3 = insertJavaRule("Another great rule CWE-1000", "1000", "rule 1000");
+    RuleDto rule4 = insertJavaRule("<h1>HTML-Geeks</h1><p style=\"color:blue\">special formatting!</p><table><tr><td>inside</td><td>tables</td></tr></table>", "404", "rule 404");
+    RuleDto rule5 = insertJavaRule("internationalization missunderstandings alsdkjfnadklsjfnadkdfnsksdjfn", "405", "rule 405");
+    index();
+
+    // partial match at word boundary
+    assertThat(underTest.search(new RuleQuery().setQueryText("CWE"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());
+
+    // full match
+    assertThat(underTest.search(new RuleQuery().setQueryText("CWE-123"), new SearchOptions()).getUuids()).containsExactly(rule1.getUuid());
+
+    // match somewhere else in the text
+    assertThat(underTest.search(new RuleQuery().setQueryText("CWE-1000"), new SearchOptions()).getUuids()).containsExactly(rule3.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("CWE 1000"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule3.getUuid(), rule1.getUuid());
+
+    // several words
+    assertThat(underTest.search(new RuleQuery().setQueryText("great rule"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(),
+      rule3.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("rule Another"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule2.getUuid(), rule3.getUuid());
+
+    // no matches
+    assertThat(underTest.search(new RuleQuery().setQueryText("unexisting"), new SearchOptions()).getUuids()).isEmpty();
+    assertThat(underTest.search(new RuleQuery().setQueryText("great rule unexisting"), new SearchOptions()).getUuids()).isEmpty();
+
+    // stopwords
+    assertThat(underTest.search(new RuleQuery().setQueryText("and"), new SearchOptions()).getUuids()).isEmpty();
+    assertThat(underTest.search(new RuleQuery().setQueryText("great and shiny"), new SearchOptions()).getUuids()).isEmpty();
+
+    // html
+    assertThat(underTest.search(new RuleQuery().setQueryText("h1"), new SearchOptions()).getUuids()).isEmpty();
+    assertThat(underTest.search(new RuleQuery().setQueryText("style"), new SearchOptions()).getUuids()).isEmpty();
+    assertThat(underTest.search(new RuleQuery().setQueryText("special"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule4.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("geeks formatting inside tables"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule4.getUuid());
+
+    // long words
+    assertThat(underTest.search(new RuleQuery().setQueryText("missunderstand"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("missunderstandings"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("alsdkjfnadklsjfnadkdfnsksdjfn"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("internationalization"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
+    assertThat(underTest.search(new RuleQuery().setQueryText("internationalizationBlaBla"), new SearchOptions()).getUuids()).isEmpty();
+  }
+
+  private RuleDto insertJavaRule(String description, String ruleKey, String name) {
+    RuleDto javaRule = newRule(createDefaultRuleDescriptionSection(uuidFactory.create(), description))
+      .setLanguage("java")
+      .setRuleKey(ruleKey)
+      .setName(name);
+    return db.rules().insert(javaRule);
+  }
+
+  @Test
+  public void search_by_any_of_repositories() {
+    RuleDto findbugs = createRule(
+      setRepositoryKey("findbugs"),
+      setRuleKey("S001"));
+    RuleDto pmd = createRule(
+      setRepositoryKey("pmd"),
+      setRuleKey("S002"));
+    index();
+
+    RuleQuery query = new RuleQuery().setRepositories(asList("checkstyle", "pmd"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsExactly(pmd.getUuid());
+
+    // no results
+    query = new RuleQuery().setRepositories(singletonList("checkstyle"));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // empty list => no filter
+    query = new RuleQuery().setRepositories(emptyList());
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(findbugs.getUuid(), pmd.getUuid());
+  }
+
+  @Test
+  public void filter_by_tags() {
+    RuleDto rule1 = createRule(setSystemTags("tag1s"), setTags("tag1"));
+    RuleDto rule2 = createRule(setSystemTags("tag2s"), setTags("tag2"));
+    index();
+
+    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(2);
+    // tag2s in filter
+    RuleQuery query = new RuleQuery().setTags(of("tag2s"));
+    verifySearch(query, rule2);
+
+    // tag2 in filter
+    query = new RuleQuery().setTags(of("tag2"));
+    verifySearch(query, rule2);
+
+    // empty list => no filter
+    query = new RuleQuery().setTags(emptySet());
+    verifySearch(query, rule1, rule2);
+
+    // null list => no filter
+    query = new RuleQuery().setTags(null);
+    verifySearch(query, rule1, rule2);
+  }
+
+  @Test
+  public void tags_facet_supports_selected_value_with_regexp_special_characters() {
+    createRule(r -> r.setTags(Set.of("misra++")));
+    index();
+
+    RuleQuery query = new RuleQuery()
+      .setTags(singletonList("misra["));
+    SearchOptions options = new SearchOptions().addFacets(FACET_TAGS);
+
+    // do not fail
+    assertThat(underTest.search(query, options).getTotal()).isZero();
+  }
+
+  @Test
+  public void search_by_types() {
+    createRule(setType(CODE_SMELL));
+    RuleDto vulnerability = createRule(setType(VULNERABILITY));
+    RuleDto bug1 = createRule(setType(BUG));
+    RuleDto bug2 = createRule(setType(BUG));
+    index();
+
+    // find all
+    RuleQuery query = new RuleQuery();
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
+
+    // type3 in filter
+    query = new RuleQuery().setTypes(of(VULNERABILITY));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(vulnerability.getUuid());
+
+    query = new RuleQuery().setTypes(of(BUG));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(bug1.getUuid(), bug2.getUuid());
+
+    // types in query => nothing
+    query = new RuleQuery().setQueryText("code smell bug vulnerability");
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // null list => no filter
+    query = new RuleQuery().setTypes(emptySet());
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
+
+    // null list => no filter
+    query = new RuleQuery().setTypes(null);
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
+  }
+
+  @Test
+  public void search_by_is_template() {
+    RuleDto ruleNoTemplate = createRule(setIsTemplate(false));
+    RuleDto ruleIsTemplate = createRule(setIsTemplate(true));
+    index();
+
+    // find all
+    RuleQuery query = new RuleQuery();
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).hasSize(2);
+
+    // Only template
+    query = new RuleQuery().setIsTemplate(true);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ruleIsTemplate.getUuid());
+
+    // Only not template
+    query = new RuleQuery().setIsTemplate(false);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ruleNoTemplate.getUuid());
+
+    // null => no filter
+    query = new RuleQuery().setIsTemplate(null);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ruleIsTemplate.getUuid(), ruleNoTemplate.getUuid());
+  }
+
+  @Test
+  public void search_by_is_external() {
+    RuleDto ruleIsNotExternal = createRule(setIsExternal(false));
+    RuleDto ruleIsExternal = createRule(setIsExternal(true));
+    index();
+
+    // Only external
+    RuleQuery query = new RuleQuery().setIncludeExternal(true);
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ruleIsExternal.getUuid(), ruleIsNotExternal.getUuid());
+
+    // Only not external
+    query = new RuleQuery().setIncludeExternal(false);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ruleIsNotExternal.getUuid());
+  }
+
+  @Test
+  public void search_by_template_key() {
+    RuleDto template = createRule(setIsTemplate(true));
+    RuleDto customRule = createRule(setTemplateId(template.getUuid()));
+    index();
+
+    // find all
+    RuleQuery query = new RuleQuery();
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).hasSize(2);
+
+    // Only custom rule
+    query = new RuleQuery().setTemplateKey(template.getKey().toString());
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(customRule.getUuid());
+
+    // null => no filter
+    query = new RuleQuery().setTemplateKey(null);
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+  }
+
+  @Test
+  public void search_by_any_of_languages() {
+    createRule(setLanguage("java"));
+    RuleDto javascript = createRule(setLanguage("js"));
+    index();
+
+    RuleQuery query = new RuleQuery().setLanguages(asList("cobol", "js"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(javascript.getUuid());
+
+    // no results
+    query = new RuleQuery().setLanguages(singletonList("cpp"));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // empty list => no filter
+    query = new RuleQuery().setLanguages(emptyList());
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+
+    // null list => no filter
+    query = new RuleQuery().setLanguages(null);
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+  }
+
+  @Test
+  public void search_by_security_cwe_return_vulnerabilities_and_hotspots_only() {
+    RuleDto rule1 = createRule(setSecurityStandards(of("cwe:543", "cwe:123", "owaspTop10:a1")), r -> r.setType(VULNERABILITY));
+    RuleDto rule2 = createRule(setSecurityStandards(of("cwe:543", "owaspTop10:a1")), r -> r.setType(SECURITY_HOTSPOT));
+    createRule(setSecurityStandards(of("owaspTop10:a1")), r -> r.setType(CODE_SMELL));
+    index();
+
+    RuleQuery query = new RuleQuery().setCwe(of("543"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("cwe"));
+    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
+  }
+
+  @Test
+  public void search_by_security_owaspTop10_2017_return_vulnerabilities_and_hotspots_only() {
+    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:543")), r -> r.setType(VULNERABILITY));
+    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT));
+    createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL));
+    index();
+
+    RuleQuery query = new RuleQuery().setOwaspTop10(of("a5", "a10"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("owaspTop10"));
+    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
+  }
+
+  @Test
+  public void search_by_security_owaspTop10_2021_return_vulnerabilities_and_hotspots_only() {
+    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10-2021:a1", "owaspTop10-2021:a10", "cwe:543")), r -> r.setType(VULNERABILITY));
+    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10-2021:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT));
+    createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL));
+    index();
+
+    RuleQuery query = new RuleQuery().setOwaspTop10For2021(of("a5", "a10"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("owaspTop10-2021"));
+    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
+  }
+
+  @Test
+  public void search_by_security_sansTop25_return_vulnerabilities_and_hotspots_only() {
+    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89")), r -> r.setType(VULNERABILITY));
+    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(SECURITY_HOTSPOT));
+    createRule(setSecurityStandards(of("cwe:306")), r -> r.setType(CODE_SMELL));
+    index();
+
+    RuleQuery query = new RuleQuery().setSansTop25(of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sansTop25"));
+    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
+  }
+
+  @Test
+  public void search_by_security_sonarsource_return_vulnerabilities_and_hotspots_only() {
+    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10-2021:a10", "cwe:89")), r -> r.setType(VULNERABILITY));
+    createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(CODE_SMELL));
+    RuleDto rule3 = createRule(setSecurityStandards(of("cwe:601")), r -> r.setType(SECURITY_HOTSPOT));
+    index();
+
+    RuleQuery query = new RuleQuery().setSonarsourceSecurity(of("sql-injection", "open-redirect"));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sonarsourceSecurity"));
+    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
+  }
+
+  @Test
+  public void search_by_security_sonarsource_return_complete_list_of_facets() {
+
+    List<RuleDto> rules = new ArrayList<>();
+
+    //Creation of one rule for each standard security category defined (except other)
+    for (Map.Entry<SecurityStandards.SQCategory, Set<String>> sqCategorySetEntry : SecurityStandards.CWES_BY_SQ_CATEGORY.entrySet()) {
+      rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), r -> r.setType(SECURITY_HOTSPOT)));
+    }
+    index();
+
+    RuleQuery query = new RuleQuery();
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sonarsourceSecurity"));
+
+    assertThat(results.getFacets().get("sonarsourceSecurity"))
+      .as("It should have as many facets returned as there are rules defined, and it is not truncated")
+      .hasSize(rules.size());
+  }
+
+  @Test
+  public void compare_to_another_profile() {
+    String xoo = "xoo";
+    QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(xoo));
+    QProfileDto anotherProfile = db.qualityProfiles().insert(p -> p.setLanguage(xoo));
+    RuleDto commonRule = db.rules().insertRule(r -> r.setLanguage(xoo));
+    RuleDto profileRule1 = db.rules().insertRule(r -> r.setLanguage(xoo));
+    RuleDto profileRule2 = db.rules().insertRule(r -> r.setLanguage(xoo));
+    RuleDto profileRule3 = db.rules().insertRule(r -> r.setLanguage(xoo));
+    RuleDto anotherProfileRule1 = db.rules().insertRule(r -> r.setLanguage(xoo));
+    RuleDto anotherProfileRule2 = db.rules().insertRule(r -> r.setLanguage(xoo));
+    db.qualityProfiles().activateRule(profile, commonRule);
+    db.qualityProfiles().activateRule(profile, profileRule1);
+    db.qualityProfiles().activateRule(profile, profileRule2);
+    db.qualityProfiles().activateRule(profile, profileRule3);
+    db.qualityProfiles().activateRule(anotherProfile, commonRule);
+    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule1);
+    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2);
+    index();
+
+    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(anotherProfile), anotherProfileRule1, anotherProfileRule2);
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(anotherProfile), commonRule);
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(profile), commonRule, profileRule1, profileRule2, profileRule3);
+    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(profile));
+  }
+
+  @SafeVarargs
+  private RuleDto createRule(Consumer<RuleDto>... consumers) {
+    return db.rules().insert(consumers);
+  }
+
+  private RuleDto createJavaRule() {
+    return createRule(r -> r.setLanguage("java"));
+  }
+
+  @Test
+  public void search_by_any_of_severities() {
+    createRule(setSeverity(BLOCKER));
+    RuleDto info = createRule(setSeverity(INFO));
+    index();
+
+    RuleQuery query = new RuleQuery().setSeverities(asList(INFO, MINOR));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(info.getUuid());
+
+    // no results
+    query = new RuleQuery().setSeverities(singletonList(MINOR));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // empty list => no filter
+    query = new RuleQuery().setSeverities(emptyList());
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+
+    // null list => no filter
+    query = new RuleQuery().setSeverities();
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+  }
+
+  @Test
+  public void search_by_any_of_statuses() {
+    createRule(setStatus(RuleStatus.BETA));
+    RuleDto ready = createRule(setStatus(RuleStatus.READY));
+    index();
+
+    RuleQuery query = new RuleQuery().setStatuses(asList(RuleStatus.DEPRECATED, RuleStatus.READY));
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsOnly(ready.getUuid());
+
+    // no results
+    query = new RuleQuery().setStatuses(singletonList(RuleStatus.DEPRECATED));
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
+
+    // empty list => no filter
+    query = new RuleQuery().setStatuses(emptyList());
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+
+    // null list => no filter
+    query = new RuleQuery().setStatuses(null);
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
+  }
+
+  @Test
+  public void activation_parameter_is_ignored_if_profile_is_not_set() {
+    RuleDto rule1 = createJavaRule();
+    RuleDto rule2 = createJavaRule();
+    QProfileDto profile1 = createJavaProfile();
+    db.qualityProfiles().activateRule(profile1, rule1);
+    index();
+
+    // all rules are returned
+    verifySearch(newRuleQuery().setActivation(true), rule1, rule2);
+  }
+
+  @Test
+  public void search_by_activation() {
+    RuleDto rule1 = createJavaRule();
+    RuleDto rule2 = createJavaRule();
+    RuleDto rule3 = createJavaRule();
+    QProfileDto profile1 = createJavaProfile();
+    QProfileDto profile2 = createJavaProfile();
+    db.qualityProfiles().activateRule(profile1, rule1);
+    db.qualityProfiles().activateRule(profile2, rule1);
+    db.qualityProfiles().activateRule(profile1, rule2);
+    index();
+
+    // active rules
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile1), rule1, rule2);
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile2), rule1);
+
+    // inactive rules
+    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile1), rule3);
+    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile2), rule2, rule3);
+  }
+
+  private void verifyEmptySearch(RuleQuery query) {
+    verifySearch(query);
+  }
+
+  private void verifySearch(RuleQuery query, RuleDto... expectedRules) {
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions());
+    assertThat(result.getTotal()).isEqualTo(expectedRules.length);
+    assertThat(result.getUuids()).hasSize(expectedRules.length);
+    for (RuleDto expectedRule : expectedRules) {
+      assertThat(result.getUuids()).contains(expectedRule.getUuid());
+    }
+  }
+
+  private void index() {
+    ruleIndexer.indexOnStartup(Sets.newHashSet(TYPE_RULE));
+    activeRuleIndexer.indexOnStartup(Sets.newHashSet(TYPE_ACTIVE_RULE));
+  }
+
+  private RuleQuery newRuleQuery() {
+    return new RuleQuery();
+  }
+
+  private QProfileDto createJavaProfile() {
+    return db.qualityProfiles().insert(p -> p.setLanguage("java"));
+  }
+
+  @Test
+  public void search_by_activation_and_inheritance() {
+    RuleDto rule1 = createJavaRule();
+    RuleDto rule2 = createJavaRule();
+    RuleDto rule3 = createJavaRule();
+    RuleDto rule4 = createJavaRule();
+    QProfileDto parent = createJavaProfile();
+    QProfileDto child = createJavaProfile();
+    db.qualityProfiles().activateRule(parent, rule1);
+    db.qualityProfiles().activateRule(parent, rule2);
+    db.qualityProfiles().activateRule(parent, rule3);
+    db.qualityProfiles().activateRule(child, rule1, ar -> ar.setInheritance(INHERITED.name()));
+    db.qualityProfiles().activateRule(child, rule2, ar -> ar.setInheritance(OVERRIDES.name()));
+    db.qualityProfiles().activateRule(child, rule3, ar -> ar.setInheritance(INHERITED.name()));
+    index();
+
+    // all rules
+    verifySearch(newRuleQuery(), rule1, rule2, rule3, rule4);
+
+    // inherited/overrides rules on parent
+    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(INHERITED.name())));
+    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(OVERRIDES.name())));
+
+    // inherited/overrides rules on child
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name())), rule1, rule3);
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(OVERRIDES.name())), rule2);
+
+    // inherited AND overridden on parent
+    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(INHERITED.name(), OVERRIDES.name())));
+
+    // inherited AND overridden on child
+    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name(), OVERRIDES.name())), rule1, rule2, rule3);
+  }
+
+  @Test
+  public void search_by_activation_and_severity() {
+    RuleDto major = createRule(setSeverity(MAJOR));
+    RuleDto minor = createRule(setSeverity(MINOR));
+    createRule(setSeverity(INFO));
+    QProfileDto profile1 = createJavaProfile();
+    QProfileDto profile2 = createJavaProfile();
+    db.qualityProfiles().activateRule(profile1, major, ar -> ar.setSeverity(BLOCKER));
+    db.qualityProfiles().activateRule(profile2, major, ar -> ar.setSeverity(BLOCKER));
+    db.qualityProfiles().activateRule(profile1, minor, ar -> ar.setSeverity(CRITICAL));
+    index();
+
+    // count activation severities of all active rules
+    RuleQuery query = newRuleQuery().setActivation(true).setQProfile(profile1);
+    verifySearch(query, major, minor);
+    verifyFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES, entry(BLOCKER, 1L), entry(CRITICAL, 1L));
+
+    // check stickyness of active severity facet
+    query = newRuleQuery().setActivation(true).setQProfile(profile1).setActiveSeverities(singletonList(CRITICAL));
+    verifySearch(query, minor);
+    verifyFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES, entry(BLOCKER, 1L), entry(CRITICAL, 1L));
+  }
+
+  @Test
+  public void facet_by_activation_severity_is_ignored_when_profile_is_not_specified() {
+    RuleDto rule = createJavaRule();
+    QProfileDto profile = createJavaProfile();
+    db.qualityProfiles().activateRule(profile, rule);
+    index();
+
+    RuleQuery query = newRuleQuery();
+    verifyNoFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES);
+  }
+
+  private void verifyFacet(RuleQuery query, String facet, Map.Entry<String, Long>... expectedBuckets) {
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(facet));
+    assertThat(result.getFacets().get(facet))
+      .containsOnly(expectedBuckets);
+  }
+
+  private void verifyNoFacet(RuleQuery query, String facet) {
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(facet));
+    assertThat(result.getFacets().get(facet)).isNull();
+  }
+
+  @Test
+  public void listTags_should_return_tags() {
+    createRule(setSystemTags("sys1", "sys2"), setTags("tag1"));
+    createRule(setSystemTags(), setTags("tag2"));
+
+    index();
+
+    assertThat(underTest.listTags(null, 10)).containsOnly("tag1", "tag2", "sys1", "sys2");
+  }
+
+  @Test
+  public void fail_to_list_tags_when_size_greater_than_500() {
+    assertThatThrownBy(() -> underTest.listTags(null, 501))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessageContaining("Page size must be lower than or equals to 500");
+  }
+
+  @Test
+  public void available_since() {
+    RuleDto ruleOld = createRule(setCreatedAt(-2_000L));
+    RuleDto ruleOlder = createRule(setCreatedAt(-1_000L));
+    index();
+
+    // 0. find all rules;
+    verifySearch(new RuleQuery(), ruleOld, ruleOlder);
+
+    // 1. find all rules available since a date;
+    RuleQuery availableSinceQuery = new RuleQuery().setAvailableSince(-1000L);
+    verifySearch(availableSinceQuery, ruleOlder);
+
+    // 2. find no new rules since tomorrow.
+    RuleQuery availableSinceNowQuery = new RuleQuery().setAvailableSince(1000L);
+    verifyEmptySearch(availableSinceNowQuery);
+  }
+
+  @Test
+  public void global_facet_on_repositories_and_tags() {
+    createRule(setRepositoryKey("php"), setSystemTags("sysTag"), setTags());
+    createRule(setRepositoryKey("php"), setSystemTags(), setTags("tag1"));
+    createRule(setRepositoryKey("javascript"), setSystemTags(), setTags("tag1", "tag2"));
+    index();
+
+    // should not have any facet!
+    RuleQuery query = new RuleQuery();
+    SearchIdResult result1 = underTest.search(query, new SearchOptions());
+    assertThat(result1.getFacets().getAll()).isEmpty();
+
+    // should not have any facet on non matching query!
+    SearchIdResult result2 = underTest.search(new RuleQuery().setQueryText("aeiou"), new SearchOptions().addFacets(singletonList("repositories")));
+    assertThat(result2.getFacets().getAll()).hasSize(1);
+    assertThat(result2.getFacets().getAll().get("repositories")).isEmpty();
+
+    // Repositories Facet is preset
+    SearchIdResult result3 = underTest.search(query, new SearchOptions().addFacets(asList("repositories", "tags")));
+    assertThat(result3.getFacets()).isNotNull();
+
+    // Verify the value of a given facet
+    Map<String, Long> repoFacets = result3.getFacets().get("repositories");
+    assertThat(repoFacets).containsOnly(entry("php", 2L), entry("javascript", 1L));
+
+    // Check that tag facet has both Tags and SystemTags values
+    Map<String, Long> tagFacets = result3.getFacets().get("tags");
+    assertThat(tagFacets).containsOnly(entry("tag1", 2L), entry("sysTag", 1L), entry("tag2", 1L));
+
+    // Check that there are no other facets
+    assertThat(result3.getFacets().getAll()).hasSize(2);
+  }
+
+  private void setupStickyFacets() {
+    createRule(setRepositoryKey("xoo"), setRuleKey("S001"), setLanguage("java"), setTags(), setSystemTags(), setType(BUG));
+    createRule(setRepositoryKey("xoo"), setRuleKey("S002"), setLanguage("java"), setTags(), setSystemTags(), setType(CODE_SMELL));
+    createRule(setRepositoryKey("xoo"), setRuleKey("S003"), setLanguage("java"), setTags(), setSystemTags("T1", "T2"), setType(CODE_SMELL));
+    createRule(setRepositoryKey("xoo"), setRuleKey("S011"), setLanguage("cobol"), setTags(), setSystemTags(), setType(CODE_SMELL));
+    createRule(setRepositoryKey("xoo"), setRuleKey("S012"), setLanguage("cobol"), setTags(), setSystemTags(), setType(BUG));
+    createRule(setRepositoryKey("foo"), setRuleKey("S013"), setLanguage("cobol"), setTags(), setSystemTags("T3", "T4"),
+      setType(VULNERABILITY));
+    createRule(setRepositoryKey("foo"), setRuleKey("S111"), setLanguage("cpp"), setTags(), setSystemTags(), setType(BUG));
+    createRule(setRepositoryKey("foo"), setRuleKey("S112"), setLanguage("cpp"), setTags(), setSystemTags(), setType(CODE_SMELL));
+    createRule(setRepositoryKey("foo"), setRuleKey("S113"), setLanguage("cpp"), setTags(), setSystemTags("T2", "T3"), setType(CODE_SMELL));
+    index();
+  }
+
+  @Test
+  public void sticky_facets_base() {
+    setupStickyFacets();
+
+    RuleQuery query = new RuleQuery();
+
+    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(9);
+  }
+
+  /**
+   * Facet with no filters at all
+   */
+  @Test
+  public void sticky_facets_no_filters() {
+    setupStickyFacets();
+    RuleQuery query = new RuleQuery();
+
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES,
+      FACET_TAGS, FACET_TYPES)));
+    Map<String, LinkedHashMap<String, Long>> facets = result.getFacets().getAll();
+    assertThat(facets).hasSize(4);
+    assertThat(facets.get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java", "cobol");
+    assertThat(facets.get(FACET_REPOSITORIES).keySet()).containsExactly("xoo", "foo");
+    assertThat(facets.get(FACET_TAGS)).containsOnlyKeys("T1", "T2", "T3", "T4");
+    assertThat(facets.get(FACET_TYPES)).containsOnlyKeys("BUG", "CODE_SMELL", "VULNERABILITY");
+  }
+
+  /**
+   * Facet with a language filter
+   * -- lang facet should still have all language
+   */
+  @Test
+  public void sticky_facets_with_1_filter() {
+    setupStickyFacets();
+    RuleQuery query = new RuleQuery().setLanguages(ImmutableList.of("cpp"));
+
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS)));
+    assertThat(result.getUuids()).hasSize(3);
+    assertThat(result.getFacets().getAll()).hasSize(3);
+    assertThat(result.getFacets().get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java", "cobol");
+    assertThat(result.getFacets().get(FACET_REPOSITORIES)).containsOnlyKeys("foo");
+    assertThat(result.getFacets().get(FACET_TAGS)).containsOnlyKeys("T2", "T3");
+  }
+
+  @Test
+  public void languages_facet_should_return_top_100_items() {
+    rangeClosed(1, 101).forEach(i -> db.rules().insert(r -> r.setLanguage("lang" + i)));
+    index();
+
+    SearchIdResult<String> result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_LANGUAGES)));
+
+    assertThat(result.getFacets().get(FACET_LANGUAGES)).hasSize(100);
+  }
+
+  @Test
+  public void repositories_facet_should_return_top_100_items() {
+    rangeClosed(1, 101).forEach(i -> db.rules().insert(r -> r.setRepositoryKey("repo" + i)));
+    index();
+
+    SearchIdResult<String> result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_REPOSITORIES)));
+
+    assertThat(result.getFacets().get(FACET_REPOSITORIES)).hasSize(100);
+  }
+
+  @Test
+  public void tags_facet_should_find_tags() {
+    createRule(setSystemTags(), setTags("bla"));
+    index();
+
+    RuleQuery query = new RuleQuery();
+    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
+
+    SearchIdResult<String> result = underTest.search(query, options);
+    assertThat(result.getFacets().get(FACET_TAGS)).contains(entry("bla", 1L));
+  }
+
+  @Test
+  public void tags_facet_should_return_top_100_items() {
+    // default number of items returned in tag facet = 100
+    String[] tags = get101Tags();
+    createRule(setSystemTags(tags));
+    index();
+
+    RuleQuery query = new RuleQuery();
+    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
+    SearchIdResult<String> result = underTest.search(query, options);
+    assertThat(result.getFacets().get(FACET_TAGS)).hasSize(100);
+    assertThat(result.getFacets().get(FACET_TAGS)).contains(entry("tag0", 1L), entry("tag25", 1L), entry("tag99", 1L));
+    assertThat(result.getFacets().get(FACET_TAGS)).doesNotContain(entry("tagA", 1L));
+  }
+
+  @Test
+  public void tags_facet_should_include_matching_selected_items() {
+    // default number of items returned in tag facet = 100
+    String[] tags = get101Tags();
+    createRule(setSystemTags(tags));
+    index();
+
+    RuleQuery query = new RuleQuery()
+      .setTags(singletonList("tagA"));
+    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
+    SearchIdResult<String> result = underTest.search(query, options);
+    assertThat(result.getFacets().get(FACET_TAGS)).hasSize(101);
+    assertThat(result.getFacets().get(FACET_TAGS).entrySet()).extracting(e -> entry(e.getKey(), e.getValue())).contains(
+
+      // check that selected item is added, although there are 100 other items
+      entry("tagA", 1L),
+
+      entry("tag0", 1L), entry("tag25", 1L), entry("tag99", 1L));
+  }
+
+  @Test
+  public void tags_facet_should_be_available() {
+    RuleQuery query = new RuleQuery();
+    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
+
+    SearchIdResult<String> result = underTest.search(query, options);
+    assertThat(result.getFacets().get(FACET_TAGS)).isNotNull();
+  }
+
+  /**
+   * Facet with 2 filters
+   * -- lang facet for tag T2
+   * -- tag facet for lang cpp
+   * -- repository for cpp & T2
+   */
+  @Test
+  public void sticky_facets_with_2_filters() {
+    setupStickyFacets();
+
+    RuleQuery query = new RuleQuery()
+      .setLanguages(ImmutableList.of("cpp"))
+      .setTags(ImmutableList.of("T2"));
+
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES,
+      FACET_TAGS)));
+    assertThat(result.getUuids()).hasSize(1);
+    Facets facets = result.getFacets();
+    assertThat(facets.getAll()).hasSize(3);
+    assertThat(facets.get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java");
+    assertThat(facets.get(FACET_REPOSITORIES)).containsOnlyKeys("foo");
+    assertThat(facets.get(FACET_TAGS)).containsOnlyKeys("T2", "T3");
+  }
+
+  /**
+   * Facet with 3 filters
+   * -- lang facet for tag T2
+   * -- tag facet for lang cpp & java
+   * -- repository for (cpp || java) & T2
+   * -- type
+   */
+  @Test
+  public void sticky_facets_with_3_filters() {
+    setupStickyFacets();
+
+    RuleQuery query = new RuleQuery()
+      .setLanguages(ImmutableList.of("cpp", "java"))
+      .setTags(ImmutableList.of("T2"))
+      .setTypes(asList(BUG, CODE_SMELL));
+
+    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS,
+      FACET_TYPES)));
+    assertThat(result.getUuids()).hasSize(2);
+    assertThat(result.getFacets().getAll()).hasSize(4);
+    assertThat(result.getFacets().get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java");
+    assertThat(result.getFacets().get(FACET_REPOSITORIES)).containsOnlyKeys("foo", "xoo");
+    assertThat(result.getFacets().get(FACET_TAGS)).containsOnlyKeys("T1", "T2", "T3");
+    assertThat(result.getFacets().get(FACET_TYPES)).containsOnlyKeys("CODE_SMELL");
+  }
+
+  @Test
+  public void sort_by_name() {
+    RuleDto abcd = createRule(setName("abcd"));
+    RuleDto abc = createRule(setName("ABC"));
+    RuleDto fgh = createRule(setName("FGH"));
+    index();
+
+    // ascending
+    RuleQuery query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME);
+    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsExactly(abc.getUuid(), abcd.getUuid(), fgh.getUuid());
+
+    // descending
+    query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME).setAscendingSort(false);
+    results = underTest.search(query, new SearchOptions());
+    assertThat(results.getUuids()).containsExactly(fgh.getUuid(), abcd.getUuid(), abc.getUuid());
+  }
+
+  @Test
+  public void default_sort_is_by_updated_at_desc() {
+    long currentTimeMillis = System.currentTimeMillis();
+
+    RuleDto old = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 1000L));
+    RuleDto oldest = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 3000L));
+    RuleDto older = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 2000L));
+    index();
+
+    SearchIdResult<String> results = underTest.search(new RuleQuery(), new SearchOptions());
+    assertThat(results.getUuids()).containsExactly(oldest.getUuid(), older.getUuid(), old.getUuid());
+  }
+
+  @Test
+  public void fail_sort_by_language() {
+    try {
+      // Sorting on a field not tagged as sortable
+      new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_LANGUAGE);
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Field 'lang' is not sortable");
+    }
+  }
+
+  @Test
+  public void paging() {
+    createRule();
+    createRule();
+    createRule();
+    index();
+
+    // from 0 to 1 included
+    SearchOptions options = new SearchOptions();
+    options.setOffset(0).setLimit(2);
+    SearchIdResult<String> results = underTest.search(new RuleQuery(), options);
+    assertThat(results.getTotal()).isEqualTo(3);
+    assertThat(results.getUuids()).hasSize(2);
+
+    // from 0 to 9 included
+    options.setOffset(0).setLimit(10);
+    results = underTest.search(new RuleQuery(), options);
+    assertThat(results.getTotal()).isEqualTo(3);
+    assertThat(results.getUuids()).hasSize(3);
+
+    // from 2 to 11 included
+    options.setOffset(2).setLimit(10);
+    results = underTest.search(new RuleQuery(), options);
+    assertThat(results.getTotal()).isEqualTo(3);
+    assertThat(results.getUuids()).hasSize(1);
+  }
+
+  @Test
+  public void search_all_keys_by_query() {
+    createRule(setRepositoryKey("javascript"), setRuleKey("X001"));
+    createRule(setRepositoryKey("cobol"), setRuleKey("X001"));
+    createRule(setRepositoryKey("php"), setRuleKey("S002"));
+    index();
+
+    // key
+    assertThat(underTest.searchAll(new RuleQuery().setQueryText("X001"))).toIterable().hasSize(2);
+
+    // partial key does not match
+    assertThat(underTest.searchAll(new RuleQuery().setQueryText("X00"))).toIterable().isEmpty();
+
+    // repo:key -> nice-to-have !
+    assertThat(underTest.searchAll(new RuleQuery().setQueryText("javascript:X001"))).toIterable().hasSize(1);
+  }
+
+  @Test
+  public void searchAll_keys_by_profile() {
+    RuleDto rule1 = createRule();
+    RuleDto rule2 = createRule();
+    RuleDto rule3 = createRule();
+    QProfileDto profile1 = createJavaProfile();
+    QProfileDto profile2 = createJavaProfile();
+    db.qualityProfiles().activateRule(profile1, rule1);
+    db.qualityProfiles().activateRule(profile2, rule1);
+    db.qualityProfiles().activateRule(profile1, rule2);
+    index();
+
+    // inactive rules on profile
+    es.getDocuments(TYPE_RULE);
+    es.getDocuments(TYPE_ACTIVE_RULE);
+    assertThat(underTest.searchAll(new RuleQuery().setActivation(false).setQProfile(profile2)))
+      .toIterable()
+      .containsOnly(rule2.getUuid(), rule3.getUuid());
+
+    // active rules on profile
+    assertThat(underTest.searchAll(new RuleQuery().setActivation(true).setQProfile(profile2)))
+      .toIterable()
+      .containsOnly(rule1.getUuid());
+  }
+
+  private String[] get101Tags() {
+    String[] tags = new String[101];
+    for (int i = 0; i < 100; i++) {
+      tags[i] = "tag" + i;
+    }
+    tags[100] = "tagA";
+    return tags;
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/rule/index/RuleIndexerIT.java
new file mode 100644 (file)
index 0000000..e096598
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.rule.index;
+
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleDto.Scope;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.security.SecurityStandards;
+import org.sonar.server.security.SecurityStandards.SQCategory;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static java.lang.String.format;
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toSet;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
+import static org.sonar.db.rule.RuleTesting.newRule;
+import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
+import static org.sonar.server.security.SecurityStandards.CWES_BY_SQ_CATEGORY;
+import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING;
+
+@RunWith(DataProviderRunner.class)
+public class RuleIndexerIT {
+
+  private static final String VALID_HOTSPOT_RULE_DESCRIPTION = "acme\n" +
+    "<h2>Ask Yourself Whether</h2>\n" +
+    "bar\n" +
+    "<h2>Recommended Secure Coding Practices</h2>\n" +
+    "foo";
+
+  private static final UuidFactoryFast uuidFactory = UuidFactoryFast.getInstance();
+  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_DTO = createDefaultRuleDescriptionSection(uuidFactory.create(), VALID_HOTSPOT_RULE_DESCRIPTION);
+  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_DTO2 = RuleDescriptionSectionDto.builder()
+    .uuid(uuidFactory.create())
+    .key("section2")
+    .content("rule descriptions section 2")
+    .build();
+
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public DbTester dbTester = DbTester.create();
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final RuleIndexer underTest = new RuleIndexer(es.client(), dbClient);
+  private final DbSession dbSession = dbTester.getSession();
+  private final RuleDto rule = new RuleDto()
+    .setUuid("rule-uuid")
+    .setRuleKey("S001")
+    .setRepositoryKey("xoo")
+    .setConfigKey("S1")
+    .setName("Null Pointer")
+    .setDescriptionFormat(RuleDto.Format.HTML)
+    .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_DTO)
+    .setLanguage("xoo")
+    .setSeverity(Severity.BLOCKER)
+    .setStatus(RuleStatus.READY)
+    .setIsTemplate(true)
+    .setSystemTags(newHashSet("cwe"))
+    .setType(RuleType.BUG)
+    .setScope(Scope.ALL)
+    .setCreatedAt(1500000000000L)
+    .setUpdatedAt(1600000000000L);
+
+  @Test
+  public void index_nothing() {
+    underTest.index(dbSession, emptyList());
+    assertThat(es.countDocuments(TYPE_RULE)).isZero();
+  }
+
+  @Test
+  public void index() {
+    dbClient.ruleDao().insert(dbSession, rule);
+    underTest.commitAndIndex(dbSession, rule.getUuid());
+
+    assertThat(es.countDocuments(TYPE_RULE)).isOne();
+  }
+
+  @Test
+  public void removed_rule_is_not_removed_from_index() {
+    // Create and Index rule
+    dbClient.ruleDao().insert(dbSession, rule.setStatus(RuleStatus.READY));
+    dbSession.commit();
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+    assertThat(es.countDocuments(TYPE_RULE)).isOne();
+
+    // Remove rule
+    dbTester.getDbClient().ruleDao().update(dbTester.getSession(), rule.setStatus(RuleStatus.READY).setUpdatedAt(2000000000000L));
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+
+    assertThat(es.countDocuments(TYPE_RULE)).isOne();
+  }
+
+  @Test
+  public void index_long_rule_description() {
+    RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(uuidFactory.create(), randomAlphanumeric(100000));
+    RuleDto rule = dbTester.rules().insert(newRule(ruleDescriptionSectionDto));
+
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+
+    assertThat(es.countDocuments(TYPE_RULE)).isOne();
+  }
+
+  @Test
+  public void index_long_rule_with_several_sections() {
+    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO, RULE_DESCRIPTION_SECTION_DTO2));
+
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+
+    List<RuleDoc> ruleDocs = es.getDocuments(TYPE_RULE, RuleDoc.class);
+    assertThat(ruleDocs).hasSize(1);
+    assertThat(ruleDocs.iterator().next().htmlDescription())
+      .contains(RULE_DESCRIPTION_SECTION_DTO.getContent())
+      .contains(RULE_DESCRIPTION_SECTION_DTO2.getContent())
+      .hasSize(RULE_DESCRIPTION_SECTION_DTO.getContent().length() + " ".length() + RULE_DESCRIPTION_SECTION_DTO2.getContent().length());
+  }
+
+  @Test
+  public void index_long_rule_with_section_in_markdown() {
+    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO).setDescriptionFormat(RuleDto.Format.MARKDOWN));
+
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+
+    List<RuleDoc> ruleDocs = es.getDocuments(TYPE_RULE, RuleDoc.class);
+    assertThat(ruleDocs).hasSize(1);
+    assertThat(ruleDocs.iterator().next().htmlDescription())
+      .isEqualTo("acme<br/>&lt;h2&gt;Ask Yourself Whether&lt;/h2&gt;<br/>bar<br/>"
+        + "&lt;h2&gt;Recommended Secure Coding Practices&lt;/h2&gt;<br/>foo");
+  }
+
+  @Test
+  @UseDataProvider("twoDifferentCategoriesButOTHERS")
+  public void log_debug_if_hotspot_rule_maps_to_multiple_SQCategories(SQCategory sqCategory1, SQCategory sqCategory2) {
+    Set<String> standards = Stream.of(sqCategory1, sqCategory2)
+      .flatMap(t -> CWES_BY_SQ_CATEGORY.get(t).stream().map(e -> "cwe:" + e))
+      .collect(toSet());
+    SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(standards);
+    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO)
+      .setType(RuleType.SECURITY_HOTSPOT)
+      .setSecurityStandards(standards));
+    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
+
+    assertThat(logTester.getLogs()).hasSize(1);
+    assertThat(logTester.logs(LoggerLevel.DEBUG).get(0))
+      .isEqualTo(format(
+        "Rule %s with CWEs '%s' maps to multiple SQ Security Categories: %s",
+        rule.getKey(),
+        String.join(", ", securityStandards.getCwe()),
+        ImmutableSet.of(sqCategory1, sqCategory2).stream()
+          .map(SQCategory::getKey)
+          .sorted(SQ_CATEGORY_KEYS_ORDERING)
+          .collect(joining(", "))));
+  }
+
+  @DataProvider
+  public static Object[][] twoDifferentCategoriesButOTHERS() {
+    EnumSet<SQCategory> sqCategories = EnumSet.allOf(SQCategory.class);
+    sqCategories.remove(SQCategory.OTHERS);
+
+    // pick two random categories
+    Random random = new Random();
+    SQCategory sqCategory1 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())];
+    sqCategories.remove(sqCategory1);
+    SQCategory sqCategory2 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())];
+    return new Object[][]{
+      {sqCategory1, sqCategory2}
+    };
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/setting/DatabaseSettingLoaderIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/setting/DatabaseSettingLoaderIT.java
new file mode 100644 (file)
index 0000000..70afe0d
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.setting;
+
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+
+public class DatabaseSettingLoaderIT {
+
+  private static final String A_KEY = "a_key";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DatabaseSettingLoader underTest = new DatabaseSettingLoader(dbTester.getDbClient());
+
+  @Test
+  public void test_load() {
+    insertPropertyIntoDb(A_KEY, "foo");
+
+    assertThat(underTest.load(A_KEY)).isEqualTo("foo");
+    assertThat(underTest.load("missing")).isNull();
+  }
+
+  @Test
+  public void null_value_in_db_is_considered_as_empty_string() {
+    insertPropertyIntoDb(A_KEY, null);
+
+    assertThat(underTest.load(A_KEY)).isEmpty();
+  }
+
+  @Test
+  public void test_empty_value_in_db() {
+    insertPropertyIntoDb(A_KEY, "");
+    assertThat(underTest.load(A_KEY)).isEmpty();
+  }
+
+  @Test
+  public void test_loadAll_with_no_properties() {
+    Map<String, String> map = underTest.loadAll();
+    assertThat(map).isEmpty();
+  }
+
+  @Test
+  public void test_loadAll() {
+    insertPropertyIntoDb("foo", "1");
+    insertPropertyIntoDb("bar", "2");
+
+    Map<String, String> map = underTest.loadAll();
+
+    assertThat(map).containsOnly(entry("foo", "1"), entry("bar", "2"));
+  }
+
+  private void insertPropertyIntoDb(String key, String value) {
+    dbTester.getDbClient().propertiesDao().saveProperty(new PropertyDto().setKey(key).setValue(value));
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/setting/ProjectConfigurationLoaderImplIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/setting/ProjectConfigurationLoaderImplIT.java
new file mode 100644 (file)
index 0000000..86abf38
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.setting;
+
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ProjectConfigurationLoaderImplIT {
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private final String globalPropKey = randomAlphanumeric(9);
+  private final String globalPropValue = randomAlphanumeric(10);
+  private final String mainBranchPropKey = randomAlphanumeric(7);
+  private final String mainBranchPropValue = randomAlphanumeric(8);
+  private final String branchPropKey = randomAlphanumeric(9);
+  private final String branchPropValue = randomAlphanumeric(10);
+
+  private final String mainBranchUuid = randomAlphanumeric(6);
+  private final String branchUuid = randomAlphanumeric(6);
+  private final MapSettings globalSettings = new MapSettings();
+  private ProjectConfigurationLoaderImpl underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new ProjectConfigurationLoaderImpl(globalSettings, db.getDbClient());
+  }
+
+  @Test
+  public void return_configuration_with_just_global_settings_when_no_component_settings() {
+    globalSettings.setProperty(mainBranchPropKey, mainBranchPropValue);
+    ComponentDto component = newComponentDto(mainBranchUuid);
+
+    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
+
+    assertThat(configuration.get(mainBranchPropKey)).contains(mainBranchPropValue);
+  }
+
+  @Test
+  public void return_configuration_with_global_settings_and_component_settings() {
+    String projectPropKey1 = randomAlphanumeric(7);
+    String projectPropValue1 = randomAlphanumeric(8);
+    String projectPropKey2 = randomAlphanumeric(9);
+    String projectPropValue2 = randomAlphanumeric(10);
+    globalSettings.setProperty(globalPropKey, globalPropValue);
+    db.properties().insertProperty(projectPropKey1, projectPropValue1, mainBranchUuid);
+    db.properties().insertProperty(projectPropKey2, projectPropValue2, mainBranchUuid);
+    ComponentDto component = newComponentDto(mainBranchUuid);
+
+    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
+
+    assertThat(configuration.get(globalPropKey)).contains(globalPropValue);
+    assertThat(configuration.get(projectPropKey1)).contains(projectPropValue1);
+    assertThat(configuration.get(projectPropKey2)).contains(projectPropValue2);
+  }
+
+  @Test
+  public void return_configuration_with_global_settings_main_branch_settings_and_branch_settings() {
+    globalSettings.setProperty(globalPropKey, globalPropValue);
+
+    db.properties().insertProperty(mainBranchPropKey, mainBranchPropValue, mainBranchUuid);
+    db.properties().insertProperty(branchPropKey, branchPropValue, branchUuid);
+
+    ComponentDto component = newComponentDto(branchUuid, mainBranchUuid);
+    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
+
+    assertThat(configuration.get(globalPropKey)).contains(globalPropValue);
+    assertThat(configuration.get(mainBranchPropKey)).contains(mainBranchPropValue);
+    assertThat(configuration.get(branchPropKey)).contains(branchPropValue);
+  }
+
+  private ComponentDto newComponentDto(String uuid) {
+    return newComponentDto(uuid, null);
+  }
+
+  private ComponentDto newComponentDto(String uuid, @Nullable String mainBranchUuid) {
+    return new ComponentDto().setUuid(uuid).setBranchUuid(uuid).setMainBranchProjectUuid(mainBranchUuid);
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexIT.java
new file mode 100644 (file)
index 0000000..e2d7afe
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.user.index;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchOptions;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
+
+public class UserIndexIT {
+
+  private static final String USER1_LOGIN = "user1";
+  private static final String USER2_LOGIN = "user2";
+  private static final String USER3_LOGIN = "user3";
+
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private UserIndex underTest = new UserIndex(es.client(), System2.INSTANCE);
+  private UserQuery.Builder userQuery = UserQuery.builder();
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_returns_the_users_with_specified_scm_account() {
+    UserDoc user1 = newUser("user1", asList("user_1", "u1"));
+    UserDoc user2 = newUser("user_with_same_email_as_user1", asList("user_2")).setEmail(user1.email());
+    UserDoc user3 = newUser("inactive_user_with_same_scm_as_user1", user1.scmAccounts()).setActive(false);
+    es.putDocuments(TYPE_USER, user1);
+    es.putDocuments(TYPE_USER, user2);
+    es.putDocuments(TYPE_USER, user3);
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.scmAccounts().get(0))).extractingResultOf("login").containsOnly(user1.login());
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.login())).extractingResultOf("login").containsOnly(user1.login());
+
+    // both users share the same email
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.email())).extracting(UserDoc::login).containsOnly(user1.login(), user2.login());
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("")).isEmpty();
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("unknown")).isEmpty();
+  }
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_ignores_inactive_user() {
+    String scmAccount = "scmA";
+    UserDoc user = newUser(USER1_LOGIN, singletonList(scmAccount)).setActive(false);
+    es.putDocuments(TYPE_USER, user);
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user.login())).isEmpty();
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(scmAccount)).isEmpty();
+  }
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_returns_maximum_three_users() {
+    String email = "user@mail.com";
+    UserDoc user1 = newUser("user1", Collections.emptyList()).setEmail(email);
+    UserDoc user2 = newUser("user2", Collections.emptyList()).setEmail(email);
+    UserDoc user3 = newUser("user3", Collections.emptyList()).setEmail(email);
+    UserDoc user4 = newUser("user4", Collections.emptyList()).setEmail(email);
+    es.putDocuments(TYPE_USER, user1);
+    es.putDocuments(TYPE_USER, user2);
+    es.putDocuments(TYPE_USER, user3);
+    es.putDocuments(TYPE_USER, user4);
+
+    // restrict results to 3 users
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(email)).hasSize(3);
+  }
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_is_case_sensitive_for_login() {
+    UserDoc user = newUser("the_login", singletonList("John.Smith"));
+    es.putDocuments(TYPE_USER, user);
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_login")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_Login")).isEmpty();
+  }
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_email() {
+    UserDoc user = newUser("the_login", "the_EMAIL@corp.com", singletonList("John.Smith"));
+    es.putDocuments(TYPE_USER, user);
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_EMAIL@corp.com")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_email@corp.com")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("email")).isEmpty();
+  }
+
+  @Test
+  public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_scm_account() {
+    UserDoc user = newUser("the_login", singletonList("John.Smith"));
+    es.putDocuments(TYPE_USER, user);
+
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("John.Smith")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN.SMIth")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN.SMITH")).hasSize(1);
+    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN")).isEmpty();
+  }
+
+  @Test
+  public void searchUsers() {
+    es.putDocuments(TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1"));
+    es.putDocuments(TYPE_USER, newUser(USER2_LOGIN, Collections.emptyList()).setEmail("email2"));
+    es.putDocuments(TYPE_USER, newUser(USER3_LOGIN, Collections.emptyList()).setEmail("email3").setActive(false));
+
+    assertThat(underTest.search(userQuery.build(), new SearchOptions()).getDocs()).hasSize(2);
+    assertThat(underTest.search(userQuery.setTextQuery("user").build(), new SearchOptions()).getDocs()).hasSize(2);
+    assertThat(underTest.search(userQuery.setTextQuery("ser").build(), new SearchOptions()).getDocs()).hasSize(2);
+    assertThat(underTest.search(userQuery.setTextQuery(USER1_LOGIN).build(), new SearchOptions()).getDocs()).hasSize(1);
+    assertThat(underTest.search(userQuery.setTextQuery(USER2_LOGIN).build(), new SearchOptions()).getDocs()).hasSize(1);
+    assertThat(underTest.search(userQuery.setTextQuery("mail").build(), new SearchOptions()).getDocs()).hasSize(2);
+    assertThat(underTest.search(userQuery.setTextQuery("EMAIL1").build(), new SearchOptions()).getDocs()).hasSize(1);
+
+    // deactivated users
+    assertThat(underTest.search(userQuery.setActive(false).setTextQuery(null).build(), new SearchOptions()).getDocs()).hasSize(1);
+    assertThat(underTest.search(userQuery.setActive(false).setTextQuery("email3").build(), new SearchOptions()).getDocs()).hasSize(1);
+  }
+
+  private static UserDoc newUser(String login, List<String> scmAccounts) {
+    return new UserDoc()
+      .setUuid(Uuids.createFast())
+      .setLogin(login)
+      .setName(login.toUpperCase(Locale.ENGLISH))
+      .setEmail(login + "@mail.com")
+      .setActive(true)
+      .setScmAccounts(scmAccounts);
+  }
+
+  private static UserDoc newUser(String login, String email, List<String> scmAccounts) {
+    return new UserDoc()
+      .setUuid(Uuids.createFast())
+      .setLogin(login)
+      .setName(login.toUpperCase(Locale.ENGLISH))
+      .setEmail(email)
+      .setActive(true)
+      .setScmAccounts(scmAccounts);
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/user/index/UserIndexerIT.java
new file mode 100644 (file)
index 0000000..b42ae32
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.user.index;
+
+import java.util.HashSet;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
+
+public class UserIndexerIT {
+
+  private final System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private final UserIndexer underTest = new UserIndexer(db.getDbClient(), es.client());
+
+  @Test
+  public void index_nothing_on_startup() {
+    underTest.indexOnStartup(new HashSet<>());
+
+    assertThat(es.countDocuments(TYPE_USER)).isZero();
+  }
+
+  @Test
+  public void indexOnStartup_adds_all_users_to_index() {
+    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("user_1", "u1")));
+
+    underTest.indexOnStartup(new HashSet<>());
+
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
+    assertThat(docs).hasSize(1);
+    UserDoc doc = docs.get(0);
+    assertThat(doc.uuid()).isEqualTo(user.getUuid());
+    assertThat(doc.login()).isEqualTo(user.getLogin());
+    assertThat(doc.name()).isEqualTo(user.getName());
+    assertThat(doc.email()).isEqualTo(user.getEmail());
+    assertThat(doc.active()).isEqualTo(user.isActive());
+    assertThat(doc.scmAccounts()).isEqualTo(user.getScmAccountsAsList());
+  }
+
+  @Test
+  public void indexAll_adds_all_users_to_index() {
+    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("user_1", "u1")));
+
+    underTest.indexAll();
+
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
+    assertThat(docs).hasSize(1);
+    UserDoc doc = docs.get(0);
+    assertThat(doc.uuid()).isEqualTo(user.getUuid());
+    assertThat(doc.login()).isEqualTo(user.getLogin());
+    assertThat(doc.name()).isEqualTo(user.getName());
+    assertThat(doc.email()).isEqualTo(user.getEmail());
+    assertThat(doc.active()).isEqualTo(user.isActive());
+    assertThat(doc.scmAccounts()).isEqualTo(user.getScmAccountsAsList());
+  }
+
+  @Test
+  public void indexOnStartup_adds_all_users() {
+    UserDto user = db.users().insertUser();
+
+    underTest.indexOnStartup(new HashSet<>());
+
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
+    assertThat(docs).hasSize(1);
+    UserDoc doc = docs.get(0);
+    assertThat(doc.uuid()).isEqualTo(user.getUuid());
+    assertThat(doc.login()).isEqualTo(user.getLogin());
+  }
+
+  @Test
+  public void commitAndIndex_single_user() {
+    UserDto user = db.users().insertUser();
+    UserDto anotherUser = db.users().insertUser();
+
+    underTest.commitAndIndex(db.getSession(), user);
+
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
+    assertThat(docs)
+      .extracting(UserDoc::uuid)
+      .containsExactlyInAnyOrder(user.getUuid())
+      .doesNotContain(anotherUser.getUuid());
+  }
+
+  @Test
+  public void commitAndIndex_multiple_users() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+
+    underTest.commitAndIndex(db.getSession(), asList(user1, user2));
+
+    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
+    assertThat(docs)
+      .extracting(UserDoc::login)
+      .containsExactlyInAnyOrder(
+        user1.getLogin(),
+        user2.getLogin());
+    assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(2);
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexIT.java
new file mode 100644 (file)
index 0000000..dca9cd2
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.view.index;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.server.es.EsTester;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
+
+public class ViewIndexIT {
+
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private ViewIndex index = new ViewIndex(es.client());
+
+  @Test
+  public void find_all_view_uuids() {
+    ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(singletonList("P1"));
+    ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(singletonList("P2"));
+    es.putDocuments(TYPE_VIEW, view1);
+    es.putDocuments(TYPE_VIEW, view2);
+
+    List<String> result = newArrayList(index.findAllViewUuids());
+
+    assertThat(result).containsOnly(view1.uuid(), view2.uuid());
+  }
+
+  @Test
+  public void not_find_all_view_uuids() {
+    List<String> result = newArrayList(index.findAllViewUuids());
+
+    assertThat(result).isEmpty();
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexerIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/view/index/ViewIndexerIT.java
new file mode 100644 (file)
index 0000000..3365bca
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.view.index;
+
+import com.google.common.collect.Maps;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.EsTester;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.resources.Qualifiers.APP;
+import static org.sonar.db.component.ComponentTesting.newProjectCopy;
+import static org.sonar.db.component.ComponentTesting.newSubPortfolio;
+import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
+
+public class ViewIndexerIT {
+
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+  @Rule
+  public DbTester db = DbTester.create();
+  @Rule
+  public EsTester es = EsTester.create();
+
+  private final DbClient dbClient = db.getDbClient();
+  private final DbSession dbSession = db.getSession();
+  private final ViewIndexer underTest = new ViewIndexer(dbClient, es.client());
+
+  @Test
+  public void getIndexTypes() {
+    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_VIEW);
+  }
+
+  @Test
+  public void index_nothing() {
+    underTest.indexOnStartup(emptySet());
+    assertThat(es.countDocuments(TYPE_VIEW)).isZero();
+  }
+
+  @Test
+  public void index_on_startup() {
+    // simple view
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto view1 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view1, t -> t.setLast(true));
+    db.components().insertComponent(newProjectCopy(project1, view1));
+    // view with subview
+    ComponentDto project2 = db.components().insertPrivateProject();
+    ComponentDto project3 = db.components().insertPrivateProject();
+    ComponentDto view2 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view2, t -> t.setLast(true));
+    db.components().insertComponent(newProjectCopy(project2, view2));
+    ComponentDto subView = db.components().insertComponent(newSubPortfolio(view2));
+    db.components().insertComponent(newProjectCopy(project3, subView));
+    // view without project
+    ComponentDto view3 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view3, t -> t.setLast(true));
+
+    underTest.indexOnStartup(emptySet());
+
+    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+    assertThat(docs).hasSize(4);
+
+    Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
+
+    assertThat(viewsByUuid.get(view1.uuid()).projects()).containsOnly(project1.uuid());
+    assertThat(viewsByUuid.get(view2.uuid()).projects()).containsOnly(project2.uuid(), project3.uuid());
+    assertThat(viewsByUuid.get(subView.uuid()).projects()).containsOnly(project3.uuid());
+    assertThat(viewsByUuid.get(view3.uuid()).projects()).isEmpty();
+  }
+
+  @Test
+  public void index_root_view() {
+    // simple view
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto view1 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view1, t -> t.setLast(true));
+    db.components().insertComponent(newProjectCopy(project1, view1));
+    // view with subview
+    ComponentDto project2 = db.components().insertPrivateProject();
+    ComponentDto project3 = db.components().insertPrivateProject();
+    ComponentDto view2 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view2, t -> t.setLast(true));
+    db.components().insertComponent(newProjectCopy(project2, view2));
+    ComponentDto subView = db.components().insertComponent(newSubPortfolio(view2));
+    db.components().insertComponent(newProjectCopy(project3, subView));
+    // view without project
+    ComponentDto view3 = db.components().insertPrivatePortfolio();
+    db.components().insertSnapshot(view3, t -> t.setLast(true));
+
+    underTest.index(view2.uuid());
+
+    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+    assertThat(docs).hasSize(2);
+
+    Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
+
+    assertThat(viewsByUuid.get(view2.uuid()).projects()).containsOnly(project2.uuid(), project3.uuid());
+    assertThat(viewsByUuid.get(subView.uuid()).projects()).containsOnly(project3.uuid());
+  }
+
+  @Test
+  public void index_view_doc() {
+    underTest.index(new ViewDoc().setUuid("EFGH").setProjects(newArrayList("KLMN", "JKLM")));
+
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+
+    assertThat(result).hasSize(1);
+    ViewDoc view = result.get(0);
+    assertThat(view.uuid()).isEqualTo("EFGH");
+    assertThat(view.projects()).containsOnly("KLMN", "JKLM");
+  }
+
+  @Test
+  public void index_application() {
+    ComponentDto application = db.components().insertPrivateApplication();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.components().insertComponent(newProjectCopy("PC1", project, application));
+
+    underTest.index(application.uuid());
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+
+    assertThat(result).hasSize(1);
+    ViewDoc resultApp = result.get(0);
+    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
+    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void index_application_on_startup() {
+    ComponentDto application = db.components().insertPrivateApplication();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.components().insertComponent(newProjectCopy("PC1", project, application));
+
+    underTest.indexOnStartup(emptySet());
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+
+    assertThat(result).hasSize(1);
+    ViewDoc resultApp = result.get(0);
+    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
+    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void index_application_with_indexAll() {
+    ComponentDto application = db.components().insertPrivateApplication();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.components().insertComponent(newProjectCopy("PC1", project, application));
+
+    underTest.indexAll();
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+
+    assertThat(result).hasSize(1);
+    ViewDoc resultApp = result.get(0);
+    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
+    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
+  }
+
+  @Test
+  public void index_application_branch() {
+    ComponentDto application = db.components().insertPublicProject(c -> c.setQualifier(APP).setKey("app"));
+    ComponentDto applicationBranch1 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch1"));
+    ComponentDto applicationBranch2 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch2"));
+    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setKey("prj1"));
+    ComponentDto project1Branch = db.components().insertProjectBranch(project1);
+    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setKey("prj2"));
+    ComponentDto project2Branch = db.components().insertProjectBranch(project2);
+    ComponentDto project3 = db.components().insertPrivateProject(p -> p.setKey("prj3"));
+    ComponentDto project3Branch = db.components().insertProjectBranch(project3);
+    db.components().insertComponent(newProjectCopy(project1Branch, applicationBranch1));
+    db.components().insertComponent(newProjectCopy(project2Branch, applicationBranch1));
+    // Technical project of applicationBranch2 should be ignored
+    db.components().insertComponent(newProjectCopy(project3Branch, applicationBranch2));
+
+    underTest.index(applicationBranch1.uuid());
+
+    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
+    assertThat(result)
+      .extracting(ViewDoc::uuid, ViewDoc::projects)
+      .containsExactlyInAnyOrder(
+        tuple(applicationBranch1.uuid(), asList(project1Branch.uuid(), project2Branch.uuid())));
+  }
+
+  @Test
+  public void delete_should_delete_the_view() {
+    ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(Collections.singletonList("P1"));
+    ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(asList("P2", "P3", "P4"));
+    ViewDoc view3 = new ViewDoc().setUuid("UUID3").setProjects(asList("P2", "P3", "P4"));
+    es.putDocuments(TYPE_VIEW, view1);
+    es.putDocuments(TYPE_VIEW, view2);
+    es.putDocuments(TYPE_VIEW, view3);
+
+    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
+      .containsOnly(view1.uuid(), view2.uuid(), view3.uuid());
+
+    underTest.delete(dbSession, asList(view1.uuid(), view2.uuid()));
+
+    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
+      .containsOnly(view3.uuid());
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/webhook/AsynchronousWebHooksImplIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/webhook/AsynchronousWebHooksImplIT.java
new file mode 100644 (file)
index 0000000..4ae1195
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.webhook;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.ce.posttask.PostProjectAnalysisTask.LogStatistics;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.webhook.WebhookDbTester;
+import org.sonar.server.async.AsyncExecution;
+
+import static java.util.Objects.requireNonNull;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newGlobalWebhook;
+
+public class AsynchronousWebHooksImplIT {
+
+  private final System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester db = create(system2);
+  private final WebhookDbTester webhookDbTester = db.webhooks();
+  private final ComponentDbTester componentDbTester = db.components();
+
+  private static final long NOW = 1_500_000_000_000L;
+
+  private final TestWebhookCaller caller = new TestWebhookCaller();
+  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
+  private final WebhookPayload mock = mock(WebhookPayload.class);
+  private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
+
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient());
+
+  @Test
+  public void send_global_webhooks() {
+    ComponentDto project = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newGlobalWebhook().setName("First").setUrl("http://url1"), null, null);
+    webhookDbTester.insert(newGlobalWebhook().setName("Second").setUrl("http://url2"), null, null);
+
+    caller.enqueueSuccess(NOW, 200, 1_234);
+    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock, mock(LogStatistics.class));
+
+    assertThat(caller.countSent()).isZero();
+    verifyNoInteractions(deliveryStorage);
+
+    asyncExecution.executeRecorded();
+
+    assertThat(caller.countSent()).isEqualTo(2);
+    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(project.uuid());
+  }
+
+  private static class RecordingAsyncExecution implements AsyncExecution {
+    private final List<Runnable> runnableList = new ArrayList<>();
+
+    @Override
+    public void addToQueue(Runnable r) {
+      runnableList.add(requireNonNull(r));
+    }
+
+    public void executeRecorded() {
+      ArrayList<Runnable> runnables = new ArrayList<>(runnableList);
+      runnableList.clear();
+      runnables.forEach(Runnable::run);
+    }
+  }
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/webhook/SynchronousWebHooksImplIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/webhook/SynchronousWebHooksImplIT.java
new file mode 100644 (file)
index 0000000..f671da1
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.webhook;
+
+import java.io.IOException;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.webhook.WebhookDbTester;
+import org.sonar.server.async.AsyncExecution;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.sonar.api.utils.log.LoggerLevel.DEBUG;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newGlobalWebhook;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
+
+public class SynchronousWebHooksImplIT {
+
+  private static final long NOW = 1_500_000_000_000L;
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester db = create();
+  private final DbClient dbClient = db.getDbClient();
+
+  private final WebhookDbTester webhookDbTester = db.webhooks();
+  private final ComponentDbTester componentDbTester = db.components();
+  private final TestWebhookCaller caller = new TestWebhookCaller();
+  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
+  private final WebhookPayload mock = mock(WebhookPayload.class);
+  private final AsyncExecution synchronousAsyncExecution = Runnable::run;
+  private final PostProjectAnalysisTask.LogStatistics taskStatistics = mock(PostProjectAnalysisTask.LogStatistics.class);
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient);
+
+  @Test
+  public void isEnabled_returns_false_if_no_webhooks() {
+    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
+    assertThat(underTest.isEnabled(projectDto)).isFalse();
+  }
+
+  @Test
+  public void isEnabled_returns_true_if_one_valid_global_webhook() {
+    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
+    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
+
+    assertThat(underTest.isEnabled(projectDto)).isTrue();
+  }
+
+  @Test
+  public void isEnabled_returns_true_if_one_valid_project_webhook() {
+    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
+    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
+
+    assertThat(underTest.isEnabled(projectDto)).isTrue();
+  }
+
+  @Test
+  public void do_nothing_if_no_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
+
+    assertThat(caller.countSent()).isZero();
+    assertThat(logTester.logs(DEBUG)).isEmpty();
+    verifyNoInteractions(deliveryStorage);
+  }
+
+  @Test
+  public void populates_log_statistics_even_if_no_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock, taskStatistics);
+
+    assertThat(caller.countSent()).isZero();
+    assertThat(logTester.logs(DEBUG)).isEmpty();
+    verifyNoInteractions(deliveryStorage);
+    verifyLogStatistics(0, 0);
+  }
+
+  @Test
+  public void send_global_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newGlobalWebhook().setName("First").setUrl("http://url1"), null, null);
+    webhookDbTester.insert(newGlobalWebhook().setName("Second").setUrl("http://url2"), null, null);
+    caller.enqueueSuccess(NOW, 200, 1_234);
+    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock, taskStatistics);
+
+    assertThat(caller.countSent()).isEqualTo(2);
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
+    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(componentDto.uuid());
+    verifyLogStatistics(2, 0);
+  }
+
+  @Test
+  public void send_project_webhooks() {
+    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
+    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
+    caller.enqueueSuccess(NOW, 200, 1_234);
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(projectDto.getUuid(), "1", "#1"), () -> mock, taskStatistics);
+
+    assertThat(caller.countSent()).isOne();
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    verify(deliveryStorage).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(projectDto.getUuid());
+    verifyLogStatistics(0, 1);
+  }
+
+  @Test
+  public void send_global_and_project_webhooks() {
+    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
+    webhookDbTester.insert(newWebhook(projectDto).setName("1First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
+    webhookDbTester.insert(newWebhook(projectDto).setName("2Second").setUrl("http://url2"), projectDto.getKey(), projectDto.getName());
+    webhookDbTester.insert(newGlobalWebhook().setName("3Third").setUrl("http://url3"), null, null);
+    webhookDbTester.insert(newGlobalWebhook().setName("4Fourth").setUrl("http://url4"), null,null);
+    webhookDbTester.insert(newGlobalWebhook().setName("5Fifth").setUrl("http://url5"), null,null);
+    caller.enqueueSuccess(NOW, 200, 1_234);
+    caller.enqueueFailure(NOW, new IOException("Fail to connect 1"));
+    caller.enqueueFailure(NOW, new IOException("Fail to connect 2"));
+    caller.enqueueSuccess(NOW, 200, 5_678);
+    caller.enqueueSuccess(NOW, 200, 9_256);
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(projectDto.getUuid(), "1", "#1"), () -> mock, taskStatistics);
+
+    assertThat(caller.countSent()).isEqualTo(5);
+    List<String> debugLogs = logTester.logs(DEBUG);
+    assertThat(debugLogs)
+      .contains("Sent webhook '1First' | url=http://url1 | time=1234ms | status=200")
+      .contains("Failed to send webhook '2Second' | url=http://url2 | message=Fail to connect 1")
+      .contains("Failed to send webhook '3Third' | url=http://url3 | message=Fail to connect 2")
+      .contains("Sent webhook '4Fourth' | url=http://url4 | time=5678ms | status=200")
+      .contains("Sent webhook '5Fifth' | url=http://url5 | time=9256ms | status=200");
+    verify(deliveryStorage, times(5)).persist(any(WebhookDelivery.class));
+    verify(deliveryStorage).purge(projectDto.getUuid());
+    verifyLogStatistics(3, 2);
+  }
+
+  private void verifyLogStatistics(int global, int project) {
+    verify(taskStatistics).add("globalWebhooks", global);
+    verify(taskStatistics).add("projectWebhooks", project);
+    verifyNoMoreInteractions(taskStatistics);
+  }
+
+}
diff --git a/server/sonar-server-common/src/it/java/org/sonar/server/webhook/WebhookDeliveryStorageIT.java b/server/sonar-server-common/src/it/java/org/sonar/server/webhook/WebhookDeliveryStorageIT.java
new file mode 100644 (file)
index 0000000..6805bfe
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.webhook;
+
+import java.io.IOException;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+import org.sonar.db.webhook.WebhookDeliveryTesting;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.webhook.WebhookDeliveryTesting.selectAllDeliveryUuids;
+
+public class WebhookDeliveryStorageIT {
+
+  private static final String DELIVERY_UUID = "abcde1234";
+  private static final long NOW = 1_500_000_000_000L;
+  private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000;
+  private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000;
+
+  private final System2 system = mock(System2.class);
+
+  @Rule
+  public final DbTester dbTester = DbTester.create(system);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbTester.getSession();
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+  private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory);
+
+  @Test
+  public void persist_generates_uuid_then_inserts_record() {
+    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+    WebhookDelivery delivery = newBuilderTemplate().build();
+
+    underTest.persist(delivery);
+
+    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+    assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID);
+    assertThat(dto.getWebhookUuid()).isEqualTo("WEBHOOK_UUID_1");
+    assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid());
+    assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid().get());
+    assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName());
+    assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl());
+    assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt());
+    assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get());
+    assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get());
+    assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().getJson());
+    assertThat(dto.getErrorStacktrace()).isNull();
+  }
+
+  @Test
+  public void persist_error_stacktrace() {
+    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setError(new IOException("fail to connect"))
+      .build();
+
+    underTest.persist(delivery);
+
+    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+    assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect");
+  }
+
+  @Test
+  public void purge_deletes_records_older_than_one_month_on_the_project() {
+    when(system.now()).thenReturn(NOW);
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO));
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO));
+    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO));
+    dbSession.commit();
+
+    underTest.purge("PROJECT_1");
+
+    // do not purge another project PROJECT_2
+    assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2", "D3");
+  }
+
+  @Test
+  public void persist_effective_url_if_present() {
+    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+    String effectiveUrl = randomAlphabetic(15);
+    WebhookDelivery delivery = newBuilderTemplate()
+      .setEffectiveUrl(effectiveUrl)
+      .build();
+
+    underTest.persist(delivery);
+
+    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+    assertThat(dto.getUrl()).isEqualTo(effectiveUrl);
+  }
+
+  private static WebhookDelivery.Builder newBuilderTemplate() {
+    return new WebhookDelivery.Builder()
+      .setWebhook(new Webhook("WEBHOOK_UUID_1", "COMPONENT1", "TASK1", RandomStringUtils.randomAlphanumeric(40),"Jenkins", "http://jenkins", null))
+      .setPayload(new WebhookPayload("my-project", "{json}"))
+      .setAt(1_000_000L)
+      .setHttpStatus(200)
+      .setDurationInMs(1_000);
+  }
+
+  private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) {
+    return WebhookDeliveryTesting.newDto()
+      .setUuid(uuid)
+      .setComponentUuid(componentUuid)
+      .setCreatedAt(at);
+  }
+}
diff --git a/server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index.xml b/server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index.xml
new file mode 100644 (file)
index 0000000..31b217d
--- /dev/null
@@ -0,0 +1,59 @@
+<dataset>
+  <rules id="1"
+         system_tags="[null]"
+         name="Avoid Cycles"
+         plugin_rule_key="AvoidCycles"
+         plugin_config_key="[null]"
+         plugin_name="squid"/>
+
+  <projects organization_uuid="org1"
+            uuid="THE_PROJECT"
+            uuid_path="NOT_USED"
+            project_uuid="THE_PROJECT"
+            path="[null]"
+            id="10"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="the_project"
+            name="TheProject"
+            private="[false]"/>
+  <projects organization_uuid="org1"
+            uuid="THE_FILE"
+            uuid_path="NOT_USED"
+            project_uuid="THE_PROJECT"
+            path="src/main/java/TheFile.java"
+            id="11"
+            scope="FIL"
+            qualifier="FIL"
+            kee="abcde"
+            name="TheFile"
+            private="[false]"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="winner"
+          author_login="[null]"
+          checksum="FFFFF"
+          gap="[null]"
+          effort="[null]"
+          message="[null]"
+          line="444"
+          component_uuid="THE_FILE"
+          project_uuid="THE_PROJECT"
+          rule_id="1"
+          reporter="[null]"
+          issue_attributes="JIRA=http://jira.com"
+          action_plan_key="[null]"
+          created_at="1500000000000"
+          updated_at="1550000000000"
+          issue_creation_date="1115848800000"
+          issue_update_date="1368828000000"
+          issue_close_date="[null]"
+          locations="[null]"
+          issue_type="2"
+  />
+</dataset>
diff --git a/server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index_project.xml b/server/sonar-server-common/src/it/resources/org/sonar/server/issue/index/IssueIndexerIT/index_project.xml
new file mode 100644 (file)
index 0000000..2800fcc
--- /dev/null
@@ -0,0 +1,110 @@
+<dataset>
+  <rules id="1"
+         system_tags="[null]"
+         name="Avoid Cycles"
+         plugin_rule_key="AvoidCycles"
+         plugin_config_key="[null]"
+         plugin_name="squid"/>
+
+  <!-- Project 1 -->
+  <projects organization_uuid="org1"
+            uuid="THE_PROJECT_1"
+            uuid_path="NOT_USED"
+            project_uuid="THE_PROJECT_1"
+            path="[null]"
+            id="10"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="the_project_1"
+            name="TheProject1"
+            private="[false]"/>
+  <projects organization_uuid="org1"
+            uuid="THE_FILE_1"
+            uuid_path="NOT_USED"
+            project_uuid="THE_PROJECT_1"
+            path="src/main/java/TheFile.java"
+            id="11"
+            scope="FIL"
+            qualifier="FIL"
+            kee="the_file_1"
+            name="TheFile1"
+            private="[false]"/>
+
+  <issues id="1"
+          kee="ABCDE"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="winner"
+          author_login="[null]"
+          checksum="FFFFF"
+          gap="[null]"
+          effort="[null]"
+          message="[null]"
+          line="444"
+          component_uuid="THE_FILE_1"
+          project_uuid="THE_PROJECT_1"
+          rule_id="1"
+          reporter="[null]"
+          issue_attributes="JIRA=http://jira.com"
+          action_plan_key="[null]"
+          created_at="1500000000000"
+          updated_at="1550000000000"
+          issue_creation_date="1115848800000"
+          issue_update_date="1368828000000"
+          issue_close_date="[null]"
+          locations="[null]"
+          issue_type="2"
+  />
+
+  <!-- Project 2 -->
+  <projects organization_uuid="org1"
+            uuid="THE_PROJECT_2"
+            uuid_path="NOT_USED"
+            path="[null]"
+            id="100"
+            scope="PRJ"
+            qualifier="TRK"
+            kee="the_project_2"
+            name="TheProject2"
+            private="[false]"/>
+  <projects organization_uuid="org1"
+            uuid="THE_FILE_2"
+            uuid_path="NOT_USED"
+            path="src/main/java/TheFile.java"
+            id="111"
+            scope="FIL"
+            qualifier="FIL"
+            kee="the_file_2"
+            name="TheFile2"
+            private="[false]"/>
+
+  <issues id="10"
+          kee="EDCBA"
+          resolution="FIXED"
+          status="RESOLVED"
+          severity="BLOCKER"
+          manual_severity="[false]"
+          assignee="winner"
+          author_login="[null]"
+          checksum="FFFFF"
+          gap="[null]"
+          effort="[null]"
+          message="[null]"
+          line="444"
+          component_uuid="THE_FILE_2"
+          project_uuid="THE_PROJECT_2"
+          rule_id="1"
+          reporter="[null]"
+          issue_attributes="JIRA=http://jira.com"
+          action_plan_key="[null]"
+          created_at="1500000000000"
+          updated_at="1550000000000"
+          issue_creation_date="1115848800000"
+          issue_update_date="1368828000000"
+          issue_close_date="[null]"
+          locations="[null]"
+          issue_type="2"
+  />
+</dataset>
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java
deleted file mode 100644 (file)
index 9cbaeec..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component.index;
-
-import java.util.Arrays;
-import java.util.Collection;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentUpdateDto;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.ProjectIndexer;
-
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
-import static org.sonar.api.resources.Qualifiers.PROJECT;
-import static org.sonar.server.component.index.ComponentIndexDefinition.FIELD_NAME;
-import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
-import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_ANALYZER;
-
-public class ComponentIndexerTest {
-
-  private System2 system2 = System2.INSTANCE;
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private DbClient dbClient = db.getDbClient();
-  private DbSession dbSession = db.getSession();
-  private ComponentIndexer underTest = new ComponentIndexer(db.getDbClient(), es.client());
-
-  @Test
-  public void test_getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_COMPONENT);
-  }
-
-  @Test
-  public void indexOnStartup_does_nothing_if_no_projects() {
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void indexOnStartup_indexes_all_components() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project1, project2);
-  }
-
-  @Test
-  public void indexOAll_indexes_all_components() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    underTest.indexAll();
-
-    assertThatIndexContainsOnly(project1, project2);
-  }
-
-  @Test
-  public void map_fields() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project);
-    ComponentDoc doc = es.getDocuments(TYPE_COMPONENT, ComponentDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(project.uuid());
-    assertThat(doc.getKey()).isEqualTo(project.getKey());
-    assertThat(doc.getProjectUuid()).isEqualTo(project.branchUuid());
-    assertThat(doc.getName()).isEqualTo(project.name());
-  }
-
-  @Test
-  public void indexOnStartup_does_not_index_non_main_branches() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void indexOnAnalysis_indexes_project() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.indexOnAnalysis(project.uuid());
-
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void indexOnAnalysis_indexes_new_components() {
-    ComponentDto project = db.components().insertPrivateProject();
-    underTest.indexOnAnalysis(project.uuid());
-    assertThatIndexContainsOnly(project);
-
-    underTest.indexOnAnalysis(project.uuid());
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void indexOnAnalysis_updates_index_on_changes() {
-    ComponentDto project = db.components().insertPrivateProject();
-    underTest.indexOnAnalysis(project.uuid());
-    assertThatComponentHasName(project, project.name());
-
-    // modify
-    project.setName("NewName");
-    updateDb(project);
-
-    // verify that index is updated
-    underTest.indexOnAnalysis(project.uuid());
-    assertThatIndexContainsOnly(project);
-    assertThatComponentHasName(project, "NewName");
-  }
-
-  @Test
-  public void indexOnAnalysis_does_not_index_non_main_branches() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
-
-    underTest.indexOnAnalysis(branch.uuid());
-
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void do_not_update_index_on_project_tag_update() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    indexProject(project, ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
-
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void do_not_update_index_on_permission_change() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    indexProject(project, ProjectIndexer.Cause.PERMISSION_CHANGE);
-
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void update_index_on_project_creation() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    IndexingResult result = indexProject(project, PROJECT_CREATION);
-
-    assertThatIndexContainsOnly(project);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-  }
-
-  @Test
-  public void delete_some_components() {
-    ComponentDto project = db.components().insertPrivateProject();
-    indexProject(project, PROJECT_CREATION);
-
-    underTest.delete(project.uuid(), emptySet());
-
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void delete_project() {
-    ComponentDto project = db.components().insertPrivateProject();
-    indexProject(project, PROJECT_CREATION);
-    assertThatIndexHasSize(1);
-
-    db.getDbClient().purgeDao().deleteProject(db.getSession(), project.uuid(), PROJECT, project.name(), project.getKey());
-    indexProject(project, PROJECT_DELETION);
-
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void errors_during_indexing_are_recovered() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    es.lockWrites(TYPE_COMPONENT);
-
-    IndexingResult result = indexProject(project1, PROJECT_CREATION);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-
-    // index is still read-only, fail to recover
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-    assertThat(es.countDocuments(TYPE_COMPONENT)).isZero();
-
-    es.unlockWrites(TYPE_COMPONENT);
-
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isZero();
-    assertThatIndexContainsOnly(project1);
-  }
-
-  private IndexingResult indexProject(ComponentDto project, ProjectIndexer.Cause cause) {
-    DbSession dbSession = db.getSession();
-    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
-    dbSession.commit();
-    return underTest.index(dbSession, items);
-  }
-
-  private void updateDb(ComponentDto component) {
-    ComponentUpdateDto updateComponent = ComponentUpdateDto.copyFrom(component);
-    updateComponent.setBChanged(true);
-    dbClient.componentDao().update(dbSession, updateComponent, component.qualifier());
-    dbClient.componentDao().applyBChangesForBranchUuid(dbSession, component.branchUuid());
-    dbSession.commit();
-  }
-
-  private IndexingResult recover() {
-    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
-    return underTest.index(db.getSession(), items);
-  }
-
-  private void assertThatIndexHasSize(int expectedSize) {
-    assertThat(es.countDocuments(TYPE_COMPONENT)).isEqualTo(expectedSize);
-  }
-
-  private void assertThatIndexContainsOnly(ComponentDto... expectedComponents) {
-    assertThat(es.getIds(TYPE_COMPONENT)).containsExactlyInAnyOrder(
-      Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new));
-  }
-
-  private void assertThatComponentHasName(ComponentDto component, String expectedName) {
-    SearchHit[] hits = es.client()
-      .search(EsClient.prepareSearch(TYPE_COMPONENT.getMainType())
-        .source(new SearchSourceBuilder()
-          .query(matchQuery(SORTABLE_ANALYZER.subField(FIELD_NAME), expectedName))))
-      .getHits()
-      .getHits();
-    assertThat(hits)
-      .extracting(SearchHit::getId)
-      .contains(component.uuid());
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/BulkIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/BulkIndexerTest.java
deleted file mode 100644 (file)
index 686e044..0000000
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.es;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
-import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
-import org.elasticsearch.action.index.IndexRequest;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.db.DbTester;
-import org.sonar.server.es.BulkIndexer.Size;
-import org.sonar.server.es.newindex.FakeIndexDefinition;
-
-import static java.util.Collections.emptyMap;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.es.newindex.FakeIndexDefinition.EXCPECTED_TYPE_FAKE;
-import static org.sonar.server.es.newindex.FakeIndexDefinition.INDEX;
-import static org.sonar.server.es.newindex.FakeIndexDefinition.TYPE_FAKE;
-
-public class BulkIndexerTest {
-
-  private final TestSystem2 testSystem2 = new TestSystem2().setNow(1_000L);
-
-  @Rule
-  public EsTester es = EsTester.createCustom(new FakeIndexDefinition().setReplicas(1));
-  @Rule
-  public DbTester dbTester = DbTester.create(testSystem2);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Test
-  public void index_nothing() {
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
-    indexer.start();
-    indexer.stop();
-
-    assertThat(count()).isZero();
-  }
-
-  @Test
-  public void index_documents() {
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR);
-    indexer.start();
-    indexer.add(newIndexRequest(42));
-    indexer.add(newIndexRequest(78));
-
-    // request is not sent yet
-    assertThat(count()).isZero();
-
-    // send remaining requests
-    indexer.stop();
-    assertThat(count()).isEqualTo(2);
-  }
-
-  @Test
-  public void large_indexing() {
-    // index has one replica
-    assertThat(replicas()).isOne();
-
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.LARGE);
-    indexer.start();
-
-    // replicas are temporarily disabled
-    assertThat(replicas()).isZero();
-
-    for (int i = 0; i < 10; i++) {
-      indexer.add(newIndexRequest(i));
-    }
-    IndexingResult result = indexer.stop();
-
-    assertThat(result.isSuccess()).isTrue();
-    assertThat(result.getSuccess()).isEqualTo(10);
-    assertThat(result.getFailures()).isZero();
-    assertThat(result.getTotal()).isEqualTo(10);
-    assertThat(count()).isEqualTo(10);
-    // replicas are re-enabled
-    assertThat(replicas()).isOne();
-  }
-
-  @Test
-  public void bulk_delete() {
-    int max = 500;
-    int removeFrom = 200;
-    FakeDoc[] docs = new FakeDoc[max];
-    for (int i = 0; i < max; i++) {
-      docs[i] = FakeIndexDefinition.newDoc(i);
-    }
-    es.putDocuments(TYPE_FAKE, docs);
-    assertThat(count()).isEqualTo(max);
-
-    SearchRequest req = EsClient.prepareSearch(TYPE_FAKE)
-      .source(new SearchSourceBuilder().query(QueryBuilders.rangeQuery(FakeIndexDefinition.INT_FIELD).gte(removeFrom)));
-    BulkIndexer.delete(es.client(), TYPE_FAKE, req);
-
-    assertThat(count()).isEqualTo(removeFrom);
-  }
-
-  @Test
-  public void listener_is_called_on_successful_requests() {
-    FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
-    indexer.start();
-    indexer.addDeletion(TYPE_FAKE, "foo");
-    indexer.stop();
-    assertThat(listener.calledDocIds)
-      .containsExactlyInAnyOrder(newDocId(EXCPECTED_TYPE_FAKE, "foo"));
-    assertThat(listener.calledResult.getSuccess()).isOne();
-    assertThat(listener.calledResult.getTotal()).isOne();
-  }
-
-  @Test
-  public void listener_is_called_even_if_deleting_a_doc_that_does_not_exist() {
-    FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
-    indexer.start();
-    indexer.add(newIndexRequestWithDocId("foo"));
-    indexer.add(newIndexRequestWithDocId("bar"));
-    indexer.stop();
-    assertThat(listener.calledDocIds)
-      .containsExactlyInAnyOrder(newDocId(EXCPECTED_TYPE_FAKE, "foo"), newDocId(EXCPECTED_TYPE_FAKE, "bar"));
-    assertThat(listener.calledResult.getSuccess()).isEqualTo(2);
-    assertThat(listener.calledResult.getTotal()).isEqualTo(2);
-  }
-
-  @Test
-  public void listener_is_not_called_with_errors() {
-    FakeListener listener = new FakeListener();
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, listener);
-    indexer.start();
-    indexer.add(newIndexRequestWithDocId("foo"));
-    indexer.add(new IndexRequest("index_does_not_exist").id("bar").source(emptyMap()));
-    indexer.stop();
-    assertThat(listener.calledDocIds).containsExactly(newDocId(EXCPECTED_TYPE_FAKE, "foo"));
-    assertThat(listener.calledResult.getSuccess()).isOne();
-    assertThat(listener.calledResult.getTotal()).isEqualTo(2);
-  }
-
-  @Test
-  public void log_requests_when_TRACE_level_is_enabled() {
-    logTester.setLevel(LoggerLevel.TRACE);
-
-    BulkIndexer indexer = new BulkIndexer(es.client(), TYPE_FAKE, Size.REGULAR, new FakeListener());
-    indexer.start();
-    indexer.add(newIndexRequestWithDocId("foo"));
-    indexer.addDeletion(TYPE_FAKE, "foo");
-    indexer.add(newIndexRequestWithDocId("bar"));
-    indexer.stop();
-
-    assertThat(logTester.logs(LoggerLevel.TRACE)
-      .stream()
-      .filter(log -> log.contains("Bulk[2 index requests on fakes/_doc, 1 delete requests on fakes/_doc]"))
-      .count()).isNotZero();
-
-  }
-
-  private static class FakeListener implements IndexingListener {
-    private final List<DocId> calledDocIds = new ArrayList<>();
-    private IndexingResult calledResult;
-
-    @Override
-    public void onSuccess(List<DocId> docIds) {
-      calledDocIds.addAll(docIds);
-    }
-
-    @Override
-    public void onFinish(IndexingResult result) {
-      calledResult = result;
-    }
-  }
-
-  private long count() {
-    return es.countDocuments(IndexType.main(Index.simple("fakes"), "fake"));
-  }
-
-  private int replicas() {
-    try {
-      GetSettingsResponse settingsResp = es.client().nativeClient().indices()
-          .getSettings(new GetSettingsRequest().indices(INDEX), RequestOptions.DEFAULT);
-      return Integer.parseInt(settingsResp.getSetting(INDEX, IndexMetadata.SETTING_NUMBER_OF_REPLICAS));
-    } catch (IOException e) {
-      throw new IllegalStateException("Could not get index settings", e);
-    }
-  }
-
-  private IndexRequest newIndexRequest(int intField) {
-    return new IndexRequest(INDEX)
-      .source(Map.of(FakeIndexDefinition.INT_FIELD, intField));
-  }
-
-  private IndexRequest newIndexRequestWithDocId(String id) {
-    return new IndexRequest(INDEX)
-      .id(id)
-      .source(Map.of(FakeIndexDefinition.INT_FIELD, 42));
-  }
-
-  private static DocId newDocId(IndexType.IndexMainType mainType, String id) {
-    return new DocId(TYPE_FAKE.getIndex().getName(), mainType.getType(), id);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/OneToManyResilientIndexingListenerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/OneToManyResilientIndexingListenerTest.java
deleted file mode 100644 (file)
index 95293d3..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.es;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.server.component.index.ComponentIndexDefinition;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
-
-public class OneToManyResilientIndexingListenerTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create();
-
-  @Test
-  public void ES_QUEUE_rows_are_deleted_when_all_docs_are_successfully_indexed() {
-    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
-    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
-    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
-    db.commit();
-
-    // does not contain outOfScopeItem
-    IndexingListener underTest = newListener(asList(item1, item2));
-
-    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
-    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
-    underTest.onSuccess(asList(issue1, issue2));
-    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
-
-    // onFinish deletes all items
-    IndexingResult result = new IndexingResult();
-    result.incrementSuccess().incrementRequests();
-    result.incrementSuccess().incrementRequests();
-    underTest.onFinish(result);
-
-    assertThatEsTableContainsOnly(outOfScopeItem);
-  }
-
-  @Test
-  public void ES_QUEUE_rows_are_not_deleted_on_partial_error() {
-    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "P1");
-    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "P2");
-    EsQueueDto outOfScopeItem = insertInQueue(ComponentIndexDefinition.TYPE_COMPONENT, "P1");
-    db.commit();
-
-    // does not contain outOfScopeItem
-    IndexingListener underTest = newListener(asList(item1, item2));
-
-    DocId issue1 = newDocId(TYPE_ISSUE, "I1");
-    DocId issue2 = newDocId(TYPE_ISSUE, "I2");
-    underTest.onSuccess(asList(issue1, issue2));
-    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
-
-    // one failure among the 2 indexing requests of issues
-    IndexingResult result = new IndexingResult();
-    result.incrementSuccess().incrementRequests();
-    result.incrementRequests();
-    underTest.onFinish(result);
-
-    assertThatEsTableContainsOnly(item1, item2, outOfScopeItem);
-  }
-
-  private static DocId newDocId(IndexType.IndexRelationType indexType, String id) {
-    IndexType.IndexMainType mainType = indexType.getMainType();
-    return new DocId(mainType.getIndex().getName(), mainType.getType(), id);
-  }
-
-  private IndexingListener newListener(Collection<EsQueueDto> items) {
-    return new OneToManyResilientIndexingListener(db.getDbClient(), db.getSession(), items);
-  }
-
-  private EsQueueDto insertInQueue(IndexType indexType, String id) {
-    EsQueueDto item = EsQueueDto.create(indexType.format(), id);
-    db.getDbClient().esQueueDao().insert(db.getSession(), singletonList(item));
-    return item;
-  }
-
-  private void assertThatEsTableContainsOnly(EsQueueDto... expected) {
-    try (DbSession otherSession = db.getDbClient().openSession(false)) {
-      List<String> uuidsInDb = db.getDbClient().esQueueDao().selectForRecovery(otherSession, Long.MAX_VALUE, 10)
-        .stream().map(EsQueueDto::getUuid).collect(toList());
-      String[] expectedUuids = Arrays.stream(expected).map(EsQueueDto::getUuid).toArray(String[]::new);
-      assertThat(uuidsInDb).containsExactlyInAnyOrder(expectedUuids);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/OneToOneResilientIndexingListenerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/OneToOneResilientIndexingListenerTest.java
deleted file mode 100644 (file)
index 8778986..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.es;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.es.EsQueueDto;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
-
-public class OneToOneResilientIndexingListenerTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create();
-
-  @Test
-  public void onSuccess_deletes_rows_from_ES_QUEUE_table() {
-    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
-    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, "bar");
-    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "baz");
-    db.commit();
-
-    IndexingListener underTest = newListener(asList(item1, item2, item3));
-
-    underTest.onSuccess(emptyList());
-    assertThatEsTableContainsOnly(item1, item2, item3);
-
-    underTest.onSuccess(asList(toDocId(item1), toDocId(item3)));
-    assertThatEsTableContainsOnly(item2);
-
-    // onFinish does nothing
-    underTest.onFinish(new IndexingResult());
-    assertThatEsTableContainsOnly(item2);
-  }
-
-  /**
-   * ES_QUEUE can contain multiple times the same document, for instance
-   * when an issue has been updated multiple times in a row without
-   * being successfully indexed.
-   * Elasticsearch response does not make difference between the different
-   * occurrences (and nevertheless it would be useless). So all the
-   * occurrences are marked as successfully indexed if a single request
-   * passes.
-   */
-  @Test
-  public void onSuccess_deletes_all_the_rows_with_same_doc_id() {
-    EsQueueDto item1 = insertInQueue(TYPE_ISSUE, "foo");
-    // same id as item1
-    EsQueueDto item2 = insertInQueue(TYPE_ISSUE, item1.getDocId());
-    EsQueueDto item3 = insertInQueue(TYPE_ISSUE, "bar");
-    db.commit();
-
-    IndexingListener underTest = newListener(asList(item1, item2, item3));
-
-    underTest.onSuccess(asList(toDocId(item1)));
-    assertThatEsTableContainsOnly(item3);
-  }
-
-  private static DocId toDocId(EsQueueDto dto) {
-    IndexType.SimpleIndexMainType mainType = IndexType.parseMainType(dto.getDocType());
-    return new DocId(mainType.getIndex(), mainType.getType(), dto.getDocId());
-  }
-
-  private IndexingListener newListener(Collection<EsQueueDto> items) {
-    return new OneToOneResilientIndexingListener(db.getDbClient(), db.getSession(), items);
-  }
-
-  private EsQueueDto insertInQueue(IndexType indexType, String id) {
-    EsQueueDto item = EsQueueDto.create(indexType.format(), id);
-    db.getDbClient().esQueueDao().insert(db.getSession(), singletonList(item));
-    return item;
-  }
-
-  private void assertThatEsTableContainsOnly(EsQueueDto... expected) {
-    try (DbSession otherSession = db.getDbClient().openSession(false)) {
-      List<String> uuidsInDb = db.getDbClient().esQueueDao().selectForRecovery(otherSession, Long.MAX_VALUE, 10)
-        .stream().map(EsQueueDto::getUuid).collect(toList());
-      String[] expectedUuids = Arrays.stream(expected).map(EsQueueDto::getUuid).toArray(String[]::new);
-      assertThat(uuidsInDb).containsExactlyInAnyOrder(expectedUuids);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/es/metadata/MetadataIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/es/metadata/MetadataIndexTest.java
deleted file mode 100644 (file)
index 133744c..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.es.metadata;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Locale;
-import java.util.Random;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Index;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.IndexType.IndexMainType;
-import org.sonar.server.es.newindex.FakeIndexDefinition;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-
-@RunWith(DataProviderRunner.class)
-public class MetadataIndexTest {
-
-  @Rule
-  public EsTester es = EsTester.createCustom(new MetadataIndexDefinitionBridge(), new FakeIndexDefinition());
-  private final MetadataIndex underTest = new MetadataIndexImpl(es.client());
-  private final String indexName = randomAlphabetic(20).toLowerCase(Locale.ENGLISH);
-  private final Index index = new Random().nextBoolean() ? Index.simple(indexName) : Index.withRelations(indexName);
-
-  @Test
-  @UseDataProvider("mainOrRelationType")
-  public void type_should_be_not_initialized_by_default(IndexType indexType) {
-    assertThat(underTest.getInitialized(indexType)).isFalse();
-  }
-
-  @Test
-  @UseDataProvider("mainOrRelationType")
-  public void type_should_be_initialized_after_explicitly_set_to_initialized(IndexType indexType) {
-
-    underTest.setInitialized(indexType, true);
-    assertThat(underTest.getInitialized(indexType)).isTrue();
-  }
-
-  @DataProvider
-  public static Object[][] mainOrRelationType() {
-    IndexMainType mainType = IndexType.main(Index.withRelations("examples"), "example");
-    return new Object[][] {
-      {mainType},
-      {IndexType.relation(mainType, "doo")}
-    };
-  }
-
-  @Test
-  public void hash_should_be_empty_by_default() {
-    assertThat(underTest.getHash(index)).isEmpty();
-  }
-
-  @Test
-  public void hash_should_be_able_to_be_automatically_set() {
-    String hash = randomAlphanumeric(20);
-    underTest.setHash(index, hash);
-    assertThat(underTest.getHash(index)).hasValue(hash);
-  }
-
-  @Test
-  public void database_metadata_are_empty_if_absent_from_index() {
-    assertThat(underTest.getDbVendor()).isNotPresent();
-  }
-
-  @Test
-  public void database_metadata_are_present_from_index() {
-    underTest.setDbMetadata("postgres");
-
-    assertThat(underTest.getDbVendor()).hasValue("postgres");
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java
deleted file mode 100644 (file)
index 2f876a9..0000000
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.favorite;
-
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.property.PropertyQuery;
-import org.sonar.db.user.UserDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-public class FavoriteUpdaterTest {
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private DbClient dbClient = db.getDbClient();
-  private DbSession dbSession = db.getSession();
-
-  private FavoriteUpdater underTest = new FavoriteUpdater(dbClient);
-
-  @Test
-  public void put_favorite() {
-    ComponentDto project = db.components().insertPrivateProject();
-    UserDto user = db.users().insertUser();
-    assertNoFavorite(project, user);
-
-    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true);
-
-    assertFavorite(project, user);
-  }
-
-  @Test
-  public void do_nothing_when_no_user() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.add(dbSession, project, null, null,true);
-
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setComponentUuid(project.uuid())
-      .build(), dbSession)).isEmpty();
-  }
-
-  @Test
-  public void do_not_add_favorite_when_already_100_favorite_projects() {
-    UserDto user = db.users().insertUser();
-    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getUuid(), user.getName()));
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .build(), dbSession)).hasSize(100);
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), false);
-
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .build(), dbSession)).hasSize(100);
-  }
-
-  @Test
-  public void do_not_add_favorite_when_already_100_favorite_portfolios() {
-    UserDto user = db.users().insertUser();
-    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(),
-      user.getUuid(), user.getLogin()));
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .build(), dbSession)).hasSize(100);
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), false);
-
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .build(), dbSession)).hasSize(100);
-  }
-
-  @Test
-  public void fail_when_more_than_100_projects_favorites() {
-    UserDto user = db.users().insertUser();
-    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(),
-      user.getUuid(), user.getLogin()));
-    ComponentDto project = db.components().insertPrivateProject();
-
-    assertThatThrownBy(() -> underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("You cannot have more than 100 favorites on components with qualifier 'TRK'");
-  }
-
-  @Test
-  public void fail_when_adding_existing_favorite() {
-    ComponentDto project = db.components().insertPrivateProject();
-    UserDto user = db.users().insertUser();
-    underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true);
-    assertFavorite(project, user);
-
-    assertThatThrownBy(() -> underTest.add(dbSession, project, user.getUuid(), user.getLogin(), true))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage(String.format("Component '%s' (uuid: %s) is already a favorite", project.getKey(), project.uuid()));
-  }
-
-  private void assertFavorite(ComponentDto project, UserDto user) {
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .setComponentUuid(project.uuid())
-      .build(), dbSession)).hasSize(1);
-  }
-
-  private void assertNoFavorite(ComponentDto project, UserDto user) {
-    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserUuid(user.getUuid())
-      .setComponentUuid(project.uuid())
-      .build(), dbSession)).isEmpty();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIndexerTest.java
deleted file mode 100644 (file)
index d005db3..0000000
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import org.assertj.core.api.Assertions;
-import org.elasticsearch.search.SearchHit;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.ProjectIndexer;
-import org.sonar.server.es.StartupIndexer;
-import org.sonar.server.permission.index.AuthorizationScope;
-import org.sonar.server.permission.index.IndexPermissions;
-import org.sonar.server.security.SecurityStandards;
-import org.sonar.server.security.SecurityStandards.SQCategory;
-import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.server.issue.IssueDocTesting.newDoc;
-import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
-import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
-import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
-
-public class IssueIndexerTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create();
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final IssueIndexer underTest = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
-
-  @Test
-  public void test_getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ISSUE);
-  }
-
-  @Test
-  public void test_getAuthorizationScope() {
-    AuthorizationScope scope = underTest.getAuthorizationScope();
-    assertThat(scope.getIndexType().getIndex()).isEqualTo(IssueIndexDefinition.DESCRIPTOR);
-    assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
-
-    Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
-    IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
-    IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
-    assertThat(projectPredicate.test(project)).isTrue();
-    assertThat(projectPredicate.test(file)).isFalse();
-  }
-
-  @Test
-  public void indexOnStartup_scrolls_db_and_adds_all_issues_to_index() {
-    IssueDto issue1 = db.issues().insert();
-    IssueDto issue2 = db.issues().insert();
-
-    underTest.indexAllIssues();
-
-    assertThatIndexHasOnly(issue1, issue2);
-  }
-
-  @Test
-  public void verify_indexed_fields() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
-    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
-    IssueDto issue = db.issues().insert(rule, project, file);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(issue.getKey());
-    assertThat(doc.assigneeUuid()).isEqualTo(issue.getAssigneeUuid());
-    assertThat(doc.authorLogin()).isEqualTo(issue.getAuthorLogin());
-    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
-    assertThat(doc.projectUuid()).isEqualTo(project.uuid());
-    assertThat(doc.branchUuid()).isEqualTo(project.uuid());
-    assertThat(doc.isMainBranch()).isTrue();
-    assertThat(doc.closeDate()).isEqualTo(issue.getIssueCloseDate());
-    assertThat(doc.creationDate()).isEqualToIgnoringMillis(issue.getIssueCreationDate());
-    assertThat(doc.directoryPath()).isEqualTo(dir.path());
-    assertThat(doc.filePath()).isEqualTo(file.path());
-    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
-    assertThat(doc.language()).isEqualTo(issue.getLanguage());
-    assertThat(doc.line()).isEqualTo(issue.getLine());
-    // functional date
-    assertThat(doc.updateDate()).isEqualToIgnoringMillis(new Date(issue.getIssueUpdateTime()));
-    assertThat(doc.getCwe()).containsExactlyInAnyOrder(SecurityStandards.UNKNOWN_STANDARD);
-    assertThat(doc.getOwaspTop10()).isEmpty();
-    assertThat(doc.getSansTop25()).isEmpty();
-    assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS);
-    assertThat(doc.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);
-  }
-
-  @Test
-  public void verify_security_standards_indexation() {
-    RuleDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123", "owaspTop10:a3", "cwe:863", "owaspAsvs-4.0:2.1.1"))));
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
-    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
-    db.issues().insert(rule, project, file);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getCwe()).containsExactlyInAnyOrder("123", "863");
-    assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder("a3");
-    assertThat(doc.getOwaspAsvs40()).containsExactlyInAnyOrder("2.1.1");
-    assertThat(doc.getSansTop25()).containsExactlyInAnyOrder(SANS_TOP_25_POROUS_DEFENSES);
-  }
-
-  @Test
-  public void indexOnStartup_does_not_fail_on_errors_and_does_enable_recovery_mode() {
-    es.lockWrites(TYPE_ISSUE);
-    db.issues().insert();
-
-    Set<IndexType> uninitializedIndexTypes = emptySet();
-    assertThatThrownBy(() -> underTest.indexOnStartup(uninitializedIndexTypes))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("SYNCHRONE StartupIndexer must implement indexOnStartup");
-    assertThatIndexHasSize(0);
-    assertThatEsQueueTableHasSize(0);
-    es.unlockWrites(TYPE_ISSUE);
-  }
-
-  @Test
-  public void indexOnAnalysis_indexes_the_issues_of_project() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    IssueDto issue = db.issues().insert(rule, project, file);
-    ComponentDto otherProject = db.components().insertPrivateProject();
-    db.components().insertComponent(newFileDto(otherProject));
-
-    underTest.indexOnAnalysis(project.uuid());
-
-    assertThatIndexHasOnly(issue);
-  }
-
-  @Test
-  public void indexOnAnalysis_does_not_delete_orphan_docs() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    IssueDto issue = db.issues().insert(rule, project, file);
-
-    // orphan in the project
-    addIssueToIndex(project.uuid(), "orphan");
-
-    underTest.indexOnAnalysis(project.uuid());
-
-    assertThat(es.getDocuments(TYPE_ISSUE))
-      .extracting(SearchHit::getId)
-      .containsExactlyInAnyOrder(issue.getKey(), "orphan");
-  }
-
-  /**
-   * Indexing recovery is handled by Compute Engine, without using
-   * the table es_queue
-   */
-  @Test
-  public void indexOnAnalysis_does_not_fail_on_errors_and_does_not_enable_recovery_mode() {
-    es.lockWrites(TYPE_ISSUE);
-    IssueDto issue = db.issues().insert();
-
-    String projectUuid = issue.getProjectUuid();
-    assertThatThrownBy(() -> underTest.indexOnAnalysis(projectUuid))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
-    assertThatIndexHasSize(0);
-    assertThatEsQueueTableHasSize(0);
-    es.unlockWrites(TYPE_ISSUE);
-  }
-
-  @Test
-  public void index_is_not_updated_when_creating_project() {
-    // it's impossible to already have an issue on a project
-    // that is being created, but it's just to verify that
-    // indexing is disabled
-    IssueDto issue = db.issues().insert();
-
-    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
-    assertThat(result.getTotal()).isZero();
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void index_is_not_updated_when_updating_project_key() {
-    // issue is inserted to verify that indexing of project is not triggered
-    IssueDto issue = db.issues().insert();
-
-    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
-    assertThat(result.getTotal()).isZero();
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void index_is_not_updated_when_updating_tags() {
-    // issue is inserted to verify that indexing of project is not triggered
-    IssueDto issue = db.issues().insert();
-
-    IndexingResult result = indexProject(issue.getProjectUuid(), ProjectIndexer.Cause.PROJECT_TAGS_UPDATE);
-    assertThat(result.getTotal()).isZero();
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void index_is_updated_when_deleting_project() {
-    addIssueToIndex("P1", "I1");
-    assertThatIndexHasSize(1);
-
-    IndexingResult result = indexProject("P1", ProjectIndexer.Cause.PROJECT_DELETION);
-
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void errors_during_project_deletion_are_recovered() {
-    addIssueToIndex("P1", "I1");
-    assertThatIndexHasSize(1);
-    es.lockWrites(TYPE_ISSUE);
-
-    IndexingResult result = indexProject("P1", ProjectIndexer.Cause.PROJECT_DELETION);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-
-    // index is still read-only, fail to recover
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-    assertThatIndexHasSize(1);
-
-    es.unlockWrites(TYPE_ISSUE);
-
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isZero();
-    assertThatIndexHasSize(0);
-  }
-
-  @Test
-  public void commitAndIndexIssues_commits_db_transaction_and_adds_issues_to_index() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-
-    // insert issues in db without committing
-    IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
-    IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
-    db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
-
-    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
-
-    // issues are persisted and indexed
-    assertThatIndexHasOnly(issue1, issue2);
-    assertThatDbHasOnly(issue1, issue2);
-    assertThatEsQueueTableHasSize(0);
-  }
-
-  @Test
-  public void commitAndIndexIssues_removes_issue_from_index_if_it_does_not_exist_in_db() {
-    IssueDto issue1 = new IssueDto().setKee("I1").setProjectUuid("P1");
-    addIssueToIndex(issue1.getProjectUuid(), issue1.getKey());
-    IssueDto issue2 = db.issues().insert();
-
-    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
-
-    // issue1 is removed from index, issue2 is persisted and indexed
-    assertThatIndexHasOnly(issue2);
-    assertThatDbHasOnly(issue2);
-    assertThatEsQueueTableHasSize(0);
-  }
-
-  @Test
-  public void indexing_errors_during_commitAndIndexIssues_are_recovered() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-
-    // insert issues in db without committing
-    IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
-    IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
-    db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
-
-    // index is read-only
-    es.lockWrites(TYPE_ISSUE);
-
-    underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
-
-    // issues are persisted but not indexed
-    assertThatIndexHasSize(0);
-    assertThatDbHasOnly(issue1, issue2);
-    assertThatEsQueueTableHasSize(2);
-
-    // re-enable write on index
-    es.unlockWrites(TYPE_ISSUE);
-
-    // emulate the recovery daemon
-    IndexingResult result = recover();
-
-    assertThatEsQueueTableHasSize(0);
-    assertThatIndexHasOnly(issue1, issue2);
-    assertThat(result.isSuccess()).isTrue();
-    assertThat(result.getTotal()).isEqualTo(2L);
-  }
-
-  @Test
-  public void recovery_does_not_fail_if_unsupported_docIdType() {
-    EsQueueDto item = EsQueueDto.create(TYPE_ISSUE.format(), "I1", "unknown", "P1");
-    db.getDbClient().esQueueDao().insert(db.getSession(), item);
-    db.commit();
-
-    recover();
-
-    assertThat(logTester.logs(LoggerLevel.ERROR))
-      .filteredOn(l -> l.contains("Unsupported es_queue.doc_id_type for issues. Manual fix is required: "))
-      .hasSize(1);
-    assertThatEsQueueTableHasSize(1);
-  }
-
-  @Test
-  public void indexing_recovers_multiple_errors_on_the_same_issue() {
-    es.lockWrites(TYPE_ISSUE);
-    IssueDto issue = db.issues().insert();
-
-    // three changes on the same issue
-    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
-    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
-    underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
-
-    assertThatIndexHasSize(0);
-    // three attempts of indexing are stored in es_queue recovery table
-    assertThatEsQueueTableHasSize(3);
-
-    es.unlockWrites(TYPE_ISSUE);
-    recover();
-
-    assertThatIndexHasOnly(issue);
-    assertThatEsQueueTableHasSize(0);
-  }
-
-  @Test
-  public void indexing_recovers_multiple_errors_on_the_same_project() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    db.issues().insert(rule, project, file);
-    db.issues().insert(rule, project, file);
-
-    es.lockWrites(TYPE_ISSUE);
-
-    IndexingResult result = indexProject(project.uuid(), ProjectIndexer.Cause.PROJECT_DELETION);
-    assertThat(result.getTotal()).isEqualTo(2L);
-    assertThat(result.getFailures()).isEqualTo(2L);
-
-    // index is still read-only, fail to recover
-    result = recover();
-    assertThat(result.getTotal()).isEqualTo(2L);
-    assertThat(result.getFailures()).isEqualTo(2L);
-    assertThatIndexHasSize(0);
-
-    es.unlockWrites(TYPE_ISSUE);
-
-    result = recover();
-    assertThat(result.getTotal()).isEqualTo(2L);
-    assertThat(result.getFailures()).isZero();
-    assertThatIndexHasSize(2);
-    assertThatEsQueueTableHasSize(0);
-  }
-
-  private IndexingResult indexProject(String projectUuid, ProjectIndexer.Cause cause) {
-    Collection<EsQueueDto> items = underTest.prepareForRecovery(db.getSession(), singletonList(projectUuid), cause);
-    db.commit();
-    return underTest.index(db.getSession(), items);
-  }
-
-  @Test
-  public void deleteByKeys_deletes_docs_by_keys() {
-    addIssueToIndex("P1", "Issue1");
-    addIssueToIndex("P1", "Issue2");
-    addIssueToIndex("P1", "Issue3");
-    addIssueToIndex("P2", "Issue4");
-
-    assertThatIndexHasOnly("Issue1", "Issue2", "Issue3", "Issue4");
-
-    underTest.deleteByKeys("P1", asList("Issue1", "Issue2"));
-
-    assertThatIndexHasOnly("Issue3", "Issue4");
-  }
-
-  @Test
-  public void deleteByKeys_does_not_recover_from_errors() {
-    addIssueToIndex("P1", "Issue1");
-    es.lockWrites(TYPE_ISSUE);
-
-    List<String> issues = List.of("Issue1");
-    assertThatThrownBy(() -> underTest.deleteByKeys("P1", issues))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
-    assertThatIndexHasOnly("Issue1");
-    assertThatEsQueueTableHasSize(0);
-    es.unlockWrites(TYPE_ISSUE);
-  }
-
-  @Test
-  public void nothing_to_do_when_delete_issues_on_empty_list() {
-    addIssueToIndex("P1", "Issue1");
-    addIssueToIndex("P1", "Issue2");
-    addIssueToIndex("P1", "Issue3");
-
-    underTest.deleteByKeys("P1", emptyList());
-
-    assertThatIndexHasOnly("Issue1", "Issue2", "Issue3");
-  }
-
-  /**
-   * This is a technical constraint, to ensure, that the indexers can be called in any order, during startup.
-   */
-  @Test
-  public void parent_child_relationship_does_not_require_ordering_of_index_requests() {
-    IssueDoc issueDoc = new IssueDoc();
-    issueDoc.setKey("key");
-    issueDoc.setProjectUuid("parent-does-not-exist");
-    new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null)
-      .index(singletonList(issueDoc).iterator());
-
-    assertThat(es.countDocuments(TYPE_ISSUE)).isOne();
-  }
-
-  @Test
-  public void index_issue_in_non_main_branch() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java/foo"));
-    ComponentDto file = db.components().insertComponent(newFileDto(branch, dir, "F1"));
-    IssueDto issue = db.issues().insert(rule, branch, file);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(issue.getKey());
-    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
-    assertThat(doc.projectUuid()).isEqualTo(branch.getMainBranchProjectUuid());
-    assertThat(doc.branchUuid()).isEqualTo(branch.uuid());
-    assertThat(doc.isMainBranch()).isFalse();
-    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
-  }
-
-  @Test
-  public void issue_on_test_file_has_test_scope() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
-    ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1").setQualifier("UTS"));
-    IssueDto issue = db.issues().insert(rule, project, file);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(issue.getKey());
-    assertThat(doc.componentUuid()).isEqualTo(file.uuid());
-    assertThat(doc.scope()).isEqualTo(IssueScope.TEST);
-  }
-
-  @Test
-  public void issue_on_directory_has_main_code_scope() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
-    IssueDto issue = db.issues().insert(rule, project, dir);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(issue.getKey());
-    assertThat(doc.componentUuid()).isEqualTo(dir.uuid());
-    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
-  }
-
-  @Test
-  public void issue_on_project_has_main_code_scope() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    IssueDto issue = db.issues().insert(rule, project, project);
-
-    underTest.indexAllIssues();
-
-    IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
-    assertThat(doc.getId()).isEqualTo(issue.getKey());
-    assertThat(doc.componentUuid()).isEqualTo(project.uuid());
-    assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
-  }
-
-  @Test
-  public void getType() {
-    Assertions.assertThat(underTest.getType()).isEqualTo(StartupIndexer.Type.ASYNCHRONOUS);
-  }
-
-  @Test
-  public void indexOnAnalysis_whenChangedComponents_shouldReindexOnlyChangedComponents() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto changedComponent1 = db.components().insertComponent(newFileDto(project));
-    ComponentDto unchangedComponent = db.components().insertComponent(newFileDto(project));
-    ComponentDto ChangedComponent2 = db.components().insertComponent(newFileDto(project));
-    IssueDto changedIssue1 = db.issues().insert(rule, project, changedComponent1);
-    IssueDto changedIssue2 = db.issues().insert(rule, project, changedComponent1);
-    IssueDto changedIssue3 = db.issues().insert(rule, project, ChangedComponent2);
-    db.issues().insert(rule, project, unchangedComponent);
-    db.issues().insert(rule, project, unchangedComponent);
-
-    underTest.indexOnAnalysis(project.uuid(), Set.of(unchangedComponent.uuid()));
-
-    assertThatIndexHasOnly(changedIssue1, changedIssue2, changedIssue3);
-  }
-
-  @Test
-  public void indexOnAnalysis_whenEmptyUnchangedComponents_shouldReindexEverything() {
-    RuleDto rule = db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto changedComponent = db.components().insertComponent(newFileDto(project));
-    IssueDto changedIssue1 = db.issues().insert(rule, project, changedComponent);
-    IssueDto changedIssue2 = db.issues().insert(rule, project, changedComponent);
-
-    underTest.indexOnAnalysis(project.uuid(), Set.of());
-
-    assertThatIndexHasOnly(changedIssue1, changedIssue2);
-  }
-
-  @Test
-  public void indexOnAnalysis_whenChangedComponentWithoutIssue_shouldReindexNothing() {
-    db.rules().insert();
-    ComponentDto project = db.components().insertPrivateProject();
-    db.components().insertComponent(newFileDto(project));
-
-    underTest.indexOnAnalysis(project.uuid(), Set.of());
-
-    assertThat(es.getDocuments(TYPE_ISSUE)).isEmpty();
-  }
-
-  private void addIssueToIndex(String projectUuid, String issueKey) {
-    es.putDocuments(TYPE_ISSUE,
-      newDoc().setKey(issueKey).setProjectUuid(projectUuid));
-  }
-
-  private void assertThatIndexHasSize(long expectedSize) {
-    assertThat(es.countDocuments(TYPE_ISSUE)).isEqualTo(expectedSize);
-  }
-
-  private void assertThatIndexHasOnly(IssueDto... expectedIssues) {
-    assertThat(es.getDocuments(TYPE_ISSUE))
-      .extracting(SearchHit::getId)
-      .containsExactlyInAnyOrder(Arrays.stream(expectedIssues).map(IssueDto::getKey).toArray(String[]::new));
-  }
-
-  private void assertThatIndexHasOnly(String... expectedKeys) {
-    List<IssueDoc> issues = es.getDocuments(TYPE_ISSUE, IssueDoc.class);
-    assertThat(issues).extracting(IssueDoc::key).containsOnly(expectedKeys);
-  }
-
-  private void assertThatEsQueueTableHasSize(int expectedSize) {
-    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
-  }
-
-  private void assertThatDbHasOnly(IssueDto... expectedIssues) {
-    try (DbSession otherSession = db.getDbClient().openSession(false)) {
-      List<String> keys = Arrays.stream(expectedIssues).map(IssueDto::getKey).collect(Collectors.toList());
-      assertThat(db.getDbClient().issueDao().selectByKeys(otherSession, keys)).hasSize(expectedIssues.length);
-    }
-  }
-
-  private IndexingResult recover() {
-    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
-    return underTest.index(db.getSession(), items);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIteratorFactoryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/index/IssueIteratorFactoryTest.java
deleted file mode 100644 (file)
index 8147a03..0000000
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.index;
-
-import com.google.common.collect.Maps;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.db.component.ComponentTesting.newDirectory;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
-
-public class IssueIteratorFactoryTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  @Test
-  public void iterator_over_one_issue() {
-    RuleDto rule = dbTester.rules().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto file = dbTester.components().insertComponent(newFileDto(project)
-      .setPath("src/main/java/Action.java"));
-    IssueDto expected = dbTester.issues().insert(rule, project, file,
-      t -> t.setResolution("FIXED")
-        .setStatus("RESOLVED")
-        .setSeverity("BLOCKER")
-        .setManualSeverity(false)
-        .setAssigneeUuid("uuid-of-guy1")
-        .setAuthorLogin("guy2")
-        .setChecksum("FFFFF")
-        .setGap(2D)
-        .setEffort(10L)
-        .setMessage(null)
-        .setLine(444)
-        .setRuleUuid(rule.getUuid())
-        .setTags(List.of("tag1", "tag2", "tag3"))
-        .setCreatedAt(1400000000000L)
-        .setUpdatedAt(1400000000000L)
-        .setIssueCreationDate(new Date(1115848800000L))
-        .setIssueUpdateDate(new Date(1356994800000L))
-        .setIssueCloseDate(null)
-        .setType(2));
-
-    Map<String, IssueDoc> issuesByKey = issuesByKey();
-
-    assertThat(issuesByKey).hasSize(1);
-
-    IssueDoc issue = issuesByKey.get(expected.getKey());
-    assertThat(issue.key()).isEqualTo(expected.getKey());
-    assertThat(issue.resolution()).isEqualTo("FIXED");
-    assertThat(issue.status()).isEqualTo("RESOLVED");
-    assertThat(issue.severity()).isEqualTo("BLOCKER");
-    assertThat(issue.assigneeUuid()).isEqualTo("uuid-of-guy1");
-    assertThat(issue.authorLogin()).isEqualTo("guy2");
-    assertThat(issue.line()).isEqualTo(444);
-    assertThat(issue.ruleUuid()).isEqualTo(rule.getUuid());
-    assertThat(issue.componentUuid()).isEqualTo(file.uuid());
-    assertThat(issue.projectUuid()).isEqualTo(file.branchUuid());
-    assertThat(issue.directoryPath()).isEqualTo("src/main/java");
-    assertThat(issue.filePath()).isEqualTo("src/main/java/Action.java");
-    assertThat(issue.getTags()).containsOnly("tag1", "tag2", "tag3");
-    assertThat(issue.effort().toMinutes()).isPositive();
-    assertThat(issue.type().getDbConstant()).isEqualTo(2);
-  }
-
-  @Test
-  public void iterator_over_issues() {
-    RuleDto rule = dbTester.rules().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, "src/main/java"));
-    ComponentDto file = dbTester.components().insertComponent(newFileDto(directory, directory)
-      .setPath("src/main/java/Action.java"));
-    IssueDto fileIssue = dbTester.issues().insert(rule, project, file,
-      t -> t
-        .setAssigneeUuid("uuid-of-guy1")
-        .setAuthorLogin("guy2")
-        .setEffort(10L)
-        .setType(1));
-    IssueDto moduleIssue = dbTester.issues().insert(rule, project, file, t -> t
-      .setAssigneeUuid("uuid-of-guy2")
-      .setAuthorLogin("guy2")
-      .setRuleUuid(rule.getUuid()));
-    IssueDto dirIssue = dbTester.issues().insert(rule, project, directory);
-    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
-
-    dbTester.issues().insertNewCodeReferenceIssue(newCodeReferenceIssue(fileIssue));
-
-    Map<String, IssueDoc> issuesByKey = issuesByKey();
-
-    assertThat(issuesByKey)
-      .hasSize(4)
-      .containsOnlyKeys(fileIssue.getKey(), moduleIssue.getKey(), dirIssue.getKey(), projectIssue.getKey());
-
-    assertThat(issuesByKey.get(fileIssue.getKey()).isNewCodeReference()).isTrue();
-  }
-
-  @Test
-  public void iterator_over_issue_from_project() {
-    RuleDto rule = dbTester.rules().insert();
-    ComponentDto project1 = dbTester.components().insertPrivateProject();
-    ComponentDto dir = dbTester.components().insertComponent(newDirectory(project1, "path"));
-    ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project1, dir));
-    String[] project1IssueKeys = Stream.of(project1, dir, file1)
-      .map(project1Component -> dbTester.issues().insert(rule, project1, project1Component).getKey())
-      .toArray(String[]::new);
-    ComponentDto project2 = dbTester.components().insertPrivateProject();
-    ComponentDto dir2 = dbTester.components().insertComponent(newDirectory(project2, "path"));
-    ComponentDto file2 = dbTester.components().insertComponent(newFileDto(project2, dir2));
-    String[] project2IssueKeys = Stream.of(project2, dir2, file2)
-      .map(project2Component -> dbTester.issues().insert(rule, project2, project2Component).getKey())
-      .toArray(String[]::new);
-
-    assertThat(issuesByKey(factory -> factory.createForBranch(project1.uuid())))
-      .containsOnlyKeys(project1IssueKeys);
-    assertThat(issuesByKey(factory -> factory.createForBranch(project2.uuid())))
-      .containsOnlyKeys(project2IssueKeys);
-    assertThat(issuesByKey(factory -> factory.createForBranch("does not exist")))
-      .isEmpty();
-  }
-
-  @Test
-  public void extract_directory_path() {
-    RuleDto rule = dbTester.rules().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto fileInRootDir = dbTester.components().insertComponent(newFileDto(project).setPath("pom.xml"));
-    ComponentDto fileInSubDir = dbTester.components().insertComponent(newFileDto(project).setPath("src/main/java/Action.java"));
-    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
-    IssueDto fileInSubDirIssue = dbTester.issues().insert(rule, project, fileInSubDir);
-    IssueDto fileInRootDirIssue = dbTester.issues().insert(rule, project, fileInRootDir);
-
-    Map<String, IssueDoc> issuesByKey = issuesByKey();
-
-    assertThat(issuesByKey).hasSize(3);
-    assertThat(issuesByKey.get(fileInSubDirIssue.getKey()).directoryPath()).isEqualTo("src/main/java");
-    assertThat(issuesByKey.get(fileInRootDirIssue.getKey()).directoryPath()).isEqualTo("/");
-    assertThat(issuesByKey.get(projectIssue.getKey()).directoryPath()).isNull();
-  }
-
-  @Test
-  public void extract_file_path() {
-    RuleDto rule = dbTester.rules().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto fileInRootDir = dbTester.components().insertComponent(newFileDto(project).setPath("pom.xml"));
-    ComponentDto fileInSubDir = dbTester.components().insertComponent(newFileDto(project).setPath("src/main/java/Action.java"));
-    IssueDto projectIssue = dbTester.issues().insert(rule, project, project);
-    IssueDto fileInSubDirIssue = dbTester.issues().insert(rule, project, fileInSubDir);
-    IssueDto fileInRootDirIssue = dbTester.issues().insert(rule, project, fileInRootDir);
-
-    Map<String, IssueDoc> issuesByKey = issuesByKey();
-
-    assertThat(issuesByKey).hasSize(3);
-    assertThat(issuesByKey.get(fileInSubDirIssue.getKey()).filePath()).isEqualTo("src/main/java/Action.java");
-    assertThat(issuesByKey.get(fileInRootDirIssue.getKey()).filePath()).isEqualTo("pom.xml");
-    assertThat(issuesByKey.get(projectIssue.getKey()).filePath()).isNull();
-  }
-
-  private Map<String, IssueDoc> issuesByKey() {
-    return issuesByKey(IssueIteratorFactory::createForAll);
-  }
-
-  private Map<String, IssueDoc> issuesByKey(Function<IssueIteratorFactory, IssueIterator> function) {
-    try (IssueIterator it = function.apply(new IssueIteratorFactory(dbTester.getDbClient()))) {
-      return Maps.uniqueIndex(it, IssueDoc::key);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
deleted file mode 100644 (file)
index cdbd37a..0000000
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.issue.notification;
-
-import java.util.Date;
-import java.util.Optional;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.Duration;
-import org.sonar.api.utils.Durations;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.issue.notification.NewIssuesNotification.DetailsSupplier;
-import org.sonar.server.issue.notification.NewIssuesNotification.RuleDefinition;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.rules.RuleType.BUG;
-import static org.sonar.api.rules.RuleType.CODE_SMELL;
-import static org.sonar.db.component.ComponentTesting.newDirectory;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE_TYPE;
-import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG;
-
-public class NewIssuesNotificationTest {
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private DetailsSupplier detailsSupplier = mock(DetailsSupplier.class);
-  private NewIssuesNotification underTest = new NewIssuesNotification(new Durations(), detailsSupplier);
-
-  @Test
-  public void set_project_without_branch() {
-    underTest.setProject("project-key", "project-long-name", null, null);
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_BRANCH)).isNull();
-  }
-
-  @Test
-  public void set_project_with_branch() {
-    underTest.setProject("project-key", "project-long-name", "feature", null);
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_BRANCH)).isEqualTo("feature");
-  }
-
-  @Test
-  public void set_project_with_pull_request() {
-    underTest.setProject("project-key", "project-long-name", null, "pr-123");
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_NAME)).isEqualTo("project-long-name");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_KEY)).isEqualTo("project-key");
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PULL_REQUEST)).isEqualTo("pr-123");
-  }
-
-  @Test
-  public void set_project_version() {
-    String version = randomAlphanumeric(5);
-
-    underTest.setProjectVersion(version);
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isEqualTo(version);
-  }
-
-  @Test
-  public void set_project_version_supports_null() {
-    underTest.setProjectVersion(null);
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isNull();
-  }
-
-  @Test
-  public void getProjectKey_returns_null_if_setProject_has_no_been_called() {
-    assertThat(underTest.getProjectKey()).isNull();
-  }
-
-  @Test
-  public void getProjectKey_returns_projectKey_if_setProject_has_been_called() {
-    String projectKey = randomAlphabetic(5);
-    String projectName = randomAlphabetic(6);
-    String branchName = randomAlphabetic(7);
-    String pullRequest = randomAlphabetic(8);
-    underTest.setProject(projectKey, projectName, branchName, pullRequest);
-
-    assertThat(underTest.getProjectKey()).isEqualTo(projectKey);
-  }
-
-  @Test
-  public void getProjectKey_returns_value_of_field_projectKey() {
-    String projectKey = randomAlphabetic(5);
-    underTest.setFieldValue("projectKey", projectKey);
-
-    assertThat(underTest.getProjectKey()).isEqualTo(projectKey);
-  }
-
-  @Test
-  public void set_date() {
-    Date date = new Date();
-
-    underTest.setAnalysisDate(date);
-
-    assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_DATE)).isEqualTo(DateUtils.formatDateTime(date));
-  }
-
-  @Test
-  public void set_statistics() {
-    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
-    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
-    ComponentDto file = db.components().insertComponent(newFileDto(directory));
-    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
-    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
-    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
-    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
-
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
-    mockDetailsSupplierComponents(project, directory, file);
-    mockDetailsSupplierRules(rule1, rule2);
-    mockDetailsSupplierAssignees(maynard, keenan);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(maynard.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo(keenan.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(TAG + ".1.label")).isEqualTo("owasp");
-    assertThat(underTest.getFieldValue(TAG + ".1.count")).isEqualTo("8");
-    assertThat(underTest.getFieldValue(TAG + ".2.label")).isEqualTo("bug");
-    assertThat(underTest.getFieldValue(TAG + ".2.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(directory.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo("Rule the World (Java)");
-    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo("Rule the Universe (Clojure)");
-    assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("3");
-    assertThat(underTest.getDefaultMessage()).startsWith("8 new issues on " + project.longName());
-  }
-
-  @Test
-  public void set_statistics_when_no_issues_created_on_current_analysis() {
-    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
-    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
-    ComponentDto file = db.components().insertComponent(newFileDto(directory));
-    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
-    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
-    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
-    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
-
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> false);
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
-    mockDetailsSupplierComponents(project, directory, file);
-    mockDetailsSupplierRules(rule1, rule2);
-    mockDetailsSupplierAssignees(maynard, keenan);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("0");
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("0");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isNull();
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isNull();
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".1.label")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".1.count")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".1.label")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".1.count")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".2.count")).isNull();
-    assertThat(underTest.getDefaultMessage()).startsWith("0 new issues on " + project.longName());
-  }
-
-  @Test
-  public void set_statistics_when_some_issues_are_no_created_on_current_analysis() {
-
-    UserDto maynard = db.users().insertUser(u -> u.setLogin("maynard"));
-    UserDto keenan = db.users().insertUser(u -> u.setLogin("keenan"));
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
-    ComponentDto file = db.components().insertComponent(newFileDto(directory));
-    RuleDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
-    RuleDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
-    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssigneeUuid(maynard.getUuid()).setTags(asList("bug", "owasp")));
-    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssigneeUuid(keenan.getUuid()).setTags(singletonList("owasp")));
-
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> i.key().equals(issue2.getKey()));
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
-    mockDetailsSupplierComponents(project, directory, file);
-    mockDetailsSupplierRules(rule1, rule2);
-    mockDetailsSupplierAssignees(maynard, keenan);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("0");
-    assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(keenan.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".1.label")).isEqualTo("owasp");
-    assertThat(underTest.getFieldValue(TAG + ".1.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(TAG + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(TAG + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(directory.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo("Rule the Universe (Clojure)");
-    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("3");
-    assertThat(underTest.getFieldValue(RULE + ".2.label")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".2.count")).isNull();
-    assertThat(underTest.getDefaultMessage()).startsWith("3 new issues on " + project.longName());
-  }
-
-  private void mockDetailsSupplierAssignees(UserDto... users) {
-    for (UserDto user : users) {
-      when(detailsSupplier.getUserNameByUuid(user.getUuid())).thenReturn(Optional.of(user.getName()));
-    }
-  }
-
-  private void mockDetailsSupplierRules(RuleDto... rules) {
-    for (RuleDto rule : rules) {
-      when(detailsSupplier.getRuleDefinitionByRuleKey(rule.getKey()))
-        .thenReturn(Optional.of(new RuleDefinition(rule.getName(), rule.getLanguage())));
-    }
-  }
-
-  private void mockDetailsSupplierComponents(ComponentDto... components) {
-    for (ComponentDto component : components) {
-      when(detailsSupplier.getComponentNameByUuid(component.uuid())).thenReturn(Optional.of(component.name()));
-    }
-  }
-
-  @Test
-  public void set_assignee() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    RuleDto rule = db.rules().insert();
-    UserDto user = db.users().insertUser();
-    IssueDto issue = db.issues().insert(rule, project, file, i -> i.setAssigneeUuid(user.getUuid()));
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue.toDefaultIssue()));
-    mockDetailsSupplierRules(rule);
-    mockDetailsSupplierAssignees(user);
-    mockDetailsSupplierComponents(project, file);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(user.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
-  }
-
-  @Test
-  public void add_only_5_assignees_with_biggest_issue_counts() {
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-    UserDto user3 = db.users().insertUser();
-    UserDto user4 = db.users().insertUser();
-    UserDto user5 = db.users().insertUser();
-    UserDto user6 = db.users().insertUser();
-    UserDto user7 = db.users().insertUser();
-    UserDto user8 = db.users().insertUser();
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    RuleDto rule = db.rules().insert();
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user1.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user2.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user3.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user4.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user5.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user6.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user7.getUuid())).toDefaultIssue()));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssigneeUuid(user8.getUuid())).toDefaultIssue()));
-    mockDetailsSupplierAssignees(user1, user2, user3, user4, user5, user6, user7, user8);
-    mockDetailsSupplierComponents(project, file);
-    mockDetailsSupplierRules(rule);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(user1.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("10");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo(user2.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("9");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.label")).isEqualTo(user3.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.count")).isEqualTo("8");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.label")).isEqualTo(user4.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.count")).isEqualTo("7");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.label")).isEqualTo(user5.getName());
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.count")).isEqualTo("6");
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".6.label")).isNull();
-    assertThat(underTest.getFieldValue(ASSIGNEE + ".6.count")).isNull();
-  }
-
-  @Test
-  public void add_only_5_components_with_biggest_issue_counts() {
-    ComponentDto project = db.components().insertPrivateProject();
-    RuleDto rule = db.rules().insert();
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    ComponentDto file1 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file1).toDefaultIssue()));
-    ComponentDto file2 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file2).toDefaultIssue()));
-    ComponentDto file3 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file3).toDefaultIssue()));
-    ComponentDto file4 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file4).toDefaultIssue()));
-    ComponentDto file5 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file5).toDefaultIssue()));
-    ComponentDto file6 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file6).toDefaultIssue()));
-    ComponentDto file7 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file7).toDefaultIssue()));
-    ComponentDto file8 = db.components().insertComponent(newFileDto(project));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file8).toDefaultIssue()));
-    mockDetailsSupplierComponents(project, file1, file2, file3, file4, file5, file6, file7, file8);
-    mockDetailsSupplierRules(rule);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file1.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("10");
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(file2.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("9");
-    assertThat(underTest.getFieldValue(COMPONENT + ".3.label")).isEqualTo(file3.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".3.count")).isEqualTo("8");
-    assertThat(underTest.getFieldValue(COMPONENT + ".4.label")).isEqualTo(file4.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".4.count")).isEqualTo("7");
-    assertThat(underTest.getFieldValue(COMPONENT + ".5.label")).isEqualTo(file5.name());
-    assertThat(underTest.getFieldValue(COMPONENT + ".5.count")).isEqualTo("6");
-    assertThat(underTest.getFieldValue(COMPONENT + ".6.label")).isNull();
-    assertThat(underTest.getFieldValue(COMPONENT + ".6.count")).isNull();
-  }
-
-  @Test
-  public void add_only_5_rules_with_biggest_issue_counts() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    RuleDto rule1 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule1, project, file).toDefaultIssue()));
-    RuleDto rule2 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule2, project, file).toDefaultIssue()));
-    RuleDto rule3 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule3, project, file).toDefaultIssue()));
-    RuleDto rule4 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule4, project, file).toDefaultIssue()));
-    RuleDto rule5 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule5, project, file).toDefaultIssue()));
-    RuleDto rule6 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule6, project, file).toDefaultIssue()));
-    RuleDto rule7 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule7, project, file).toDefaultIssue()));
-    RuleDto rule8 = db.rules().insert(r -> r.setLanguage("Java"));
-    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule8, project, file).toDefaultIssue()));
-    mockDetailsSupplierComponents(project, file);
-    mockDetailsSupplierRules(rule1, rule2, rule3, rule4, rule5, rule6, rule7, rule8);
-
-    underTest.setStatistics(project.longName(), stats);
-
-    String javaSuffix = " (Java)";
-    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo(rule1.getName() + javaSuffix);
-    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("10");
-    assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo(rule2.getName() + javaSuffix);
-    assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("9");
-    assertThat(underTest.getFieldValue(RULE + ".3.label")).isEqualTo(rule3.getName() + javaSuffix);
-    assertThat(underTest.getFieldValue(RULE + ".3.count")).isEqualTo("8");
-    assertThat(underTest.getFieldValue(RULE + ".4.label")).isEqualTo(rule4.getName() + javaSuffix);
-    assertThat(underTest.getFieldValue(RULE + ".4.count")).isEqualTo("7");
-    assertThat(underTest.getFieldValue(RULE + ".5.label")).isEqualTo(rule5.getName() + javaSuffix);
-    assertThat(underTest.getFieldValue(RULE + ".5.count")).isEqualTo("6");
-    assertThat(underTest.getFieldValue(RULE + ".6.label")).isNull();
-    assertThat(underTest.getFieldValue(RULE + ".6.count")).isNull();
-  }
-
-  @Test
-  public void set_debt() {
-    underTest.setDebt(Duration.create(55));
-
-    assertThat(underTest.getFieldValue(EFFORT + ".count")).isEqualTo("55min");
-  }
-
-  @Test
-  public void RuleDefinition_implements_equals_base_on_name_and_language() {
-    String name = randomAlphabetic(5);
-    String language = randomAlphabetic(6);
-    RuleDefinition underTest = new RuleDefinition(name, language);
-
-    assertThat(underTest)
-      .isEqualTo(underTest)
-      .isEqualTo(new RuleDefinition(name, language))
-      .isNotEqualTo(new RuleDefinition(language, name))
-      .isNotEqualTo(new RuleDefinition(randomAlphabetic(7), name))
-      .isNotEqualTo(new RuleDefinition(language, randomAlphabetic(7)))
-      .isNotEqualTo(new RuleDefinition(language, null))
-      .isNotNull()
-      .isNotEqualTo(new Object());
-  }
-
-  @Test
-  public void RuleDefinition_implements_hashcode_base_on_name_and_language() {
-    String name = randomAlphabetic(5);
-    String language = randomAlphabetic(6);
-    RuleDefinition underTest = new RuleDefinition(name, language);
-
-    assertThat(underTest)
-      .hasSameHashCodeAs(underTest)
-      .hasSameHashCodeAs(new RuleDefinition(name, language));
-
-    assertThat(underTest.hashCode())
-      .isNotEqualTo(new RuleDefinition(language, name).hashCode())
-      .isNotEqualTo(new RuleDefinition(randomAlphabetic(7), name).hashCode())
-      .isNotEqualTo(new RuleDefinition(language, randomAlphabetic(7)).hashCode())
-      .isNotEqualTo(new RuleDefinition(language, null).hashCode())
-      .isNotEqualTo(new Object().hashCode());
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java
deleted file mode 100644 (file)
index 294e2f3..0000000
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.measure.index;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import org.elasticsearch.action.search.SearchRequest;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.IndexingResult;
-import org.sonar.server.es.ProjectIndexer;
-import org.sonar.server.permission.index.AuthorizationScope;
-import org.sonar.server.permission.index.IndexPermissions;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termQuery;
-import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.server.es.EsClient.prepareSearch;
-import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_CREATION;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_DELETION;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_KEY_UPDATE;
-import static org.sonar.server.es.ProjectIndexer.Cause.PROJECT_TAGS_UPDATE;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_QUALIFIER;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_UUID;
-import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
-import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
-
-public class ProjectMeasuresIndexerTest {
-
-  private final System2 system2 = System2.INSTANCE;
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final ProjectMeasuresIndexer underTest = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
-
-  @Test
-  public void test_getAuthorizationScope() {
-    AuthorizationScope scope = underTest.getAuthorizationScope();
-    assertThat(scope.getIndexType().getIndex()).isEqualTo(ProjectMeasuresIndexDefinition.DESCRIPTOR);
-    assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
-
-    Predicate<IndexPermissions> projectPredicate = scope.getProjectPredicate();
-    IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
-    IndexPermissions app = new IndexPermissions("P1", Qualifiers.APP);
-    IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
-    assertThat(projectPredicate.test(project)).isTrue();
-    assertThat(projectPredicate.test(app)).isTrue();
-    assertThat(projectPredicate.test(file)).isFalse();
-  }
-
-  @Test
-  public void index_nothing() {
-    underTest.indexOnStartup(emptySet());
-
-    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
-  }
-
-  @Test
-  public void indexOnStartup_indexes_all_projects() {
-    SnapshotDto project1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto project2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto project3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project1, project2, project3);
-    assertThatQualifierIs("TRK", project1, project2, project3);
-  }
-
-  @Test
-  public void indexAll_indexes_all_projects() {
-    SnapshotDto project1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto project2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto project3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-
-    underTest.indexAll();
-
-    assertThatIndexContainsOnly(project1, project2, project3);
-    assertThatQualifierIs("TRK", project1, project2, project3);
-  }
-
-  /**
-   * Provisioned projects don't have analysis yet
-   */
-  @Test
-  public void indexOnStartup_indexes_provisioned_projects() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void indexOnStartup_ignores_non_main_branches() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void indexOnStartup_indexes_all_applications() {
-    ComponentDto application1 = db.components().insertPrivateApplication();
-    ComponentDto application2 = db.components().insertPrivateApplication();
-    ComponentDto application3 = db.components().insertPrivateApplication();
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(application1, application2, application3);
-    assertThatQualifierIs("APP", application1, application2, application3);
-  }
-
-  @Test
-  public void indexOnStartup_indexes_projects_and_applications() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-    ComponentDto project3 = db.components().insertPrivateProject();
-
-    ComponentDto application1 = db.components().insertPrivateApplication();
-    ComponentDto application2 = db.components().insertPrivateApplication();
-    ComponentDto application3 = db.components().insertPrivateApplication();
-
-    underTest.indexOnStartup(emptySet());
-
-    assertThatIndexContainsOnly(project1, project2, project3, application1, application2, application3);
-    assertThatQualifierIs("TRK", project1, project2, project3);
-    assertThatQualifierIs("APP", application1, application2, application3);
-  }
-
-  @Test
-  public void indexOnAnalysis_indexes_provisioned_project() {
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto project2 = db.components().insertPrivateProject();
-
-    underTest.indexOnAnalysis(project1.uuid());
-
-    assertThatIndexContainsOnly(project1);
-  }
-
-  @Test
-  public void indexOnAnalysis_indexes_provisioned_application() {
-    ComponentDto app1 = db.components().insertPrivateApplication();
-    ComponentDto app2 = db.components().insertPrivateApplication();
-
-    underTest.indexOnAnalysis(app1.uuid());
-
-    assertThatIndexContainsOnly(app1);
-  }
-
-  @Test
-  public void update_index_when_project_key_is_updated() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    IndexingResult result = indexProject(project, PROJECT_KEY_UPDATE);
-
-    assertThatIndexContainsOnly(project);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-  }
-
-  @Test
-  public void update_index_when_project_is_created() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    IndexingResult result = indexProject(project, PROJECT_CREATION);
-
-    assertThatIndexContainsOnly(project);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-  }
-
-  @Test
-  public void update_index_when_project_tags_are_updated() {
-    ComponentDto project = db.components().insertPrivateProject(defaults(), p -> p.setTagsString("foo"));
-    indexProject(project, PROJECT_CREATION);
-    assertThatProjectHasTag(project, "foo");
-
-    ProjectDto projectDto = db.components().getProjectDto(project);
-    projectDto.setTagsString("bar");
-    db.getDbClient().projectDao().updateTags(db.getSession(), projectDto);
-    // TODO change indexing?
-    IndexingResult result = indexProject(project, PROJECT_TAGS_UPDATE);
-
-    assertThatProjectHasTag(project, "bar");
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-  }
-
-  @Test
-  public void delete_doc_from_index_when_project_is_deleted() {
-    ComponentDto project = db.components().insertPrivateProject();
-    indexProject(project, PROJECT_CREATION);
-    assertThatIndexContainsOnly(project);
-
-    db.getDbClient().purgeDao().deleteProject(db.getSession(), project.uuid(), Qualifiers.PROJECT, project.name(), project.getKey());
-    IndexingResult result = indexProject(project, PROJECT_DELETION);
-
-    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getSuccess()).isOne();
-  }
-
-  @Test
-  public void do_nothing_if_no_projects_and_apps_to_index() {
-    // this project should not be indexed
-    db.components().insertPrivateProject();
-    db.components().insertPrivateApplication();
-
-    underTest.index(db.getSession(), emptyList());
-
-    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
-  }
-
-  @Test
-  public void errors_during_indexing_are_recovered() {
-    ComponentDto project = db.components().insertPrivateProject();
-    es.lockWrites(TYPE_PROJECT_MEASURES);
-
-    IndexingResult result = indexProject(project, PROJECT_CREATION);
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-
-    // index is still read-only, fail to recover
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isOne();
-    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
-    assertThatEsQueueTableHasSize(1);
-
-    es.unlockWrites(TYPE_PROJECT_MEASURES);
-
-    result = recover();
-    assertThat(result.getTotal()).isOne();
-    assertThat(result.getFailures()).isZero();
-    assertThatEsQueueTableHasSize(0);
-    assertThatIndexContainsOnly(project);
-  }
-
-  @Test
-  public void non_main_branches_are_not_indexed_during_analysis() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
-
-    underTest.indexOnAnalysis(branch.uuid());
-
-    assertThat(es.countDocuments(TYPE_PROJECT_MEASURES)).isZero();
-  }
-
-  private IndexingResult indexProject(ComponentDto project, ProjectIndexer.Cause cause) {
-    DbSession dbSession = db.getSession();
-    Collection<EsQueueDto> items = underTest.prepareForRecovery(dbSession, singletonList(project.uuid()), cause);
-    dbSession.commit();
-    return underTest.index(dbSession, items);
-  }
-
-  private void assertThatProjectHasTag(ComponentDto project, String expectedTag) {
-    SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
-      .source(new SearchSourceBuilder()
-        .query(boolQuery()
-          .filter(termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()))
-          .filter(termQuery(FIELD_TAGS, expectedTag))));
-
-    assertThat(es.client().search(request).getHits().getHits())
-      .extracting(SearchHit::getId)
-      .contains(project.uuid());
-  }
-
-  private void assertThatEsQueueTableHasSize(int expectedSize) {
-    assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
-  }
-
-  private void assertThatIndexContainsOnly(SnapshotDto... expectedProjects) {
-    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
-      Arrays.stream(expectedProjects).map(SnapshotDto::getComponentUuid).toArray(String[]::new));
-  }
-
-  private void assertThatIndexContainsOnly(ComponentDto... expectedProjects) {
-    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
-      Arrays.stream(expectedProjects).map(ComponentDto::uuid).toArray(String[]::new));
-  }
-
-  private void assertThatQualifierIs(String qualifier, ComponentDto... expectedComponents) {
-    String[] expectedComponentUuids = Arrays.stream(expectedComponents).map(ComponentDto::uuid).toArray(String[]::new);
-    assertThatQualifierIs(qualifier, expectedComponentUuids);
-  }
-
-  private void assertThatQualifierIs(String qualifier, SnapshotDto... expectedComponents) {
-    String[] expectedComponentUuids = Arrays.stream(expectedComponents).map(SnapshotDto::getComponentUuid).toArray(String[]::new);
-    assertThatQualifierIs(qualifier, expectedComponentUuids);
-  }
-
-  private void assertThatQualifierIs(String qualifier, String... componentsUuid) {
-    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
-      .query(boolQuery()
-        .filter(termQuery(FIELD_INDEX_TYPE, TYPE_PROJECT_MEASURES.getName()))
-        .filter(termQuery(FIELD_QUALIFIER, qualifier))
-        .filter(termsQuery(FIELD_UUID, componentsUuid)));
-
-    SearchRequest request = prepareSearch(TYPE_PROJECT_MEASURES.getMainType())
-      .source(searchSourceBuilder);
-    assertThat(es.client().search(request).getHits().getHits())
-      .extracting(SearchHit::getId)
-      .containsExactlyInAnyOrder(componentsUuid);
-  }
-
-  private IndexingResult recover() {
-    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
-    return underTest.index(db.getSession(), items);
-  }
-
-  private static <T> Consumer<T> defaults() {
-    return t -> {
-      // do nothing
-    };
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/metric/MetricFinderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/metric/MetricFinderTest.java
deleted file mode 100644 (file)
index 69bf0f7..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.metric;
-
-import java.util.Arrays;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.metric.MetricDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.db.metric.MetricTesting.newMetricDto;
-
-public class MetricFinderTest {
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final MetricFinder underTest = new MetricFinder(db.getDbClient());
-
-  @Test
-  public void findAll_enabled() {
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
-    db.commit();
-
-    assertThat(underTest.findAll()).hasSize(2);
-  }
-
-  @Test
-  public void findAll_by_keys() {
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("ncloc"));
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("foo"));
-    db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setKey("coverage"));
-    db.commit();
-
-    assertThat(underTest.findAll(Arrays.asList("ncloc", "foo"))).extracting(Metric::getKey).containsExactlyInAnyOrder("ncloc", "foo")
-      .doesNotContain("coverage");
-
-  }
-
-  @Test
-  public void findById() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.commit();
-
-    assertThat(underTest.findByUuid(firstMetric.getUuid())).extracting(Metric::getKey).isEqualTo(firstMetric.getKey());
-  }
-
-  @Test
-  public void findById_filters_out_disabled() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
-    db.commit();
-
-    assertThat(underTest.findByUuid(secondMetric.getUuid())).isNull();
-  }
-
-  @Test
-  public void findById_doesnt_find_anything() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.commit();
-
-    assertThat(underTest.findByUuid("non existing")).isNull();
-  }
-
-  @Test
-  public void findByKey() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.commit();
-
-    assertThat(underTest.findByKey(secondMetric.getKey())).extracting(Metric::getKey).isEqualTo(secondMetric.getKey());
-  }
-
-  @Test
-  public void findByKey_filters_out_disabled() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto().setEnabled(false));
-    db.commit();
-
-    assertThat(underTest.findByKey(secondMetric.getKey())).isNull();
-  }
-
-  @Test
-  public void findByKey_doesnt_find_anything() {
-    MetricDto firstMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    MetricDto secondMetric = db.getDbClient().metricDao().insert(db.getSession(), newMetricDto());
-    db.commit();
-
-    assertThat(underTest.findByKey("doesnt exist")).isNull();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/platform/StartupMetadataProviderTest.java
deleted file mode 100644 (file)
index a2bc420..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.SonarEdition;
-import org.sonar.api.SonarQubeSide;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.internal.SonarRuntimeImpl;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.Version;
-import org.sonar.db.DbTester;
-import org.sonar.db.property.PropertyDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.utils.DateUtils.formatDateTime;
-
-public class StartupMetadataProviderTest {
-  private static final long A_DATE = 1_500_000_000_000L;
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final StartupMetadataProvider underTest = new StartupMetadataProvider();
-  private final System2 system = mock(System2.class);
-  private final NodeInformation nodeInformation = mock(NodeInformation.class);
-
-  @Test
-  public void generate_SERVER_STARTIME_but_do_not_persist_it_if_server_is_startup_leader() {
-    when(system.now()).thenReturn(A_DATE);
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER, SonarEdition.COMMUNITY);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    StartupMetadata metadata = underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient());
-    assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
-
-    assertNotPersistedProperty(CoreProperties.SERVER_STARTTIME);
-  }
-
-  @Test
-  public void load_from_database_if_server_is_startup_follower() {
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.SERVER, SonarEdition.COMMUNITY);
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    testLoadingFromDatabase(runtime, false);
-  }
-
-  @Test
-  public void load_from_database_if_compute_engine_of_startup_leader_server() {
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
-
-    testLoadingFromDatabase(runtime, true);
-  }
-
-  @Test
-  public void load_from_database_if_compute_engine_of_startup_follower_server() {
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
-
-    testLoadingFromDatabase(runtime, false);
-  }
-
-  @Test
-  public void fail_to_load_from_database_if_properties_are_not_persisted() {
-    SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.create(6, 1), SonarQubeSide.COMPUTE_ENGINE, SonarEdition.COMMUNITY);
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    assertThatThrownBy(() -> underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Property sonar.core.startTime is missing in database");
-  }
-
-  private void testLoadingFromDatabase(SonarRuntime runtime, boolean isStartupLeader) {
-    dbTester.properties().insertProperty(new PropertyDto().setKey(CoreProperties.SERVER_STARTTIME).setValue(formatDateTime(A_DATE)),
-      null, null,null, null);
-    when(nodeInformation.isStartupLeader()).thenReturn(isStartupLeader);
-
-    StartupMetadata metadata = underTest.provide(system, runtime, nodeInformation, dbTester.getDbClient());
-    assertThat(metadata.getStartedAt()).isEqualTo(A_DATE);
-
-    // still in database
-    assertPersistedProperty(CoreProperties.SERVER_STARTTIME, formatDateTime(A_DATE));
-
-    verifyNoInteractions(system);
-  }
-
-  private void assertPersistedProperty(String propertyKey, String expectedValue) {
-    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
-    assertThat(prop.getValue()).isEqualTo(expectedValue);
-  }
-
-  private void assertNotPersistedProperty(String propertyKey) {
-    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
-    assertThat(prop).isNull();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/platform/monitoring/DbSectionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/platform/monitoring/DbSectionTest.java
deleted file mode 100644 (file)
index 8a17a07..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.platform.monitoring;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.SQLException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
-
-public class DbSectionTest {
-
-  @Rule
-  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  @Test
-  public void db_info() {
-    DbSection underTest = new DbSection(dbTester.getDbClient());
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
-    SystemInfoTesting.assertThatAttributeIs(section, "Database", "H2");
-    assertThat(attribute(section, "Database Version").getStringValue()).startsWith("2.");
-    SystemInfoTesting.assertThatAttributeIs(section, "Username", "SONAR");
-    assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("2.");
-    SystemInfoTesting.assertThatAttributeIs(section, "Default transaction isolation", "TRANSACTION_READ_COMMITTED");
-  }
-
-  @Test
-  public void db_info_unknown_transaction_isolation() throws SQLException {
-    DbClient dbClient = prepareClientWithUnknownTransactionLevel();
-
-    DbSection underTest = new DbSection(dbClient);
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
-    SystemInfoTesting.assertThatAttributeIs(section, "Default transaction isolation", "Unknown transaction level: 42");
-  }
-
-  private static DbClient prepareClientWithUnknownTransactionLevel() throws SQLException {
-    DbClient dbClient = mock(DbClient.class);
-    DbSession dbSession = mock(DbSession.class);
-    Connection connection = mock(Connection.class);
-    DatabaseMetaData metadata = mock(DatabaseMetaData.class);
-
-    when(dbClient.openSession(false)).thenReturn(dbSession);
-    when(dbSession.getConnection()).thenReturn(connection);
-    when(connection.getMetaData()).thenReturn(metadata);
-    when(metadata.getDefaultTransactionIsolation()).thenReturn(42);
-    return dbClient;
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
deleted file mode 100644 (file)
index 52fe087..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualitygate;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-public class QualityGateFinderTest {
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final DbSession dbSession = db.getSession();
-
-  private final QualityGateFinder underTest = new QualityGateFinder(db.getDbClient());
-
-  @Test
-  public void return_default_quality_gate_for_project() {
-    ProjectDto project = db.components().insertPrivateProjectDto();
-    QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
-
-    QualityGateFinder.QualityGateData result = underTest.getEffectiveQualityGate(dbSession, project);
-
-    assertThat(result.getUuid()).isEqualTo(dbQualityGate.getUuid());
-    assertThat(result.isDefault()).isTrue();
-  }
-
-  @Test
-  public void return_project_quality_gate_over_default() {
-    ProjectDto project = db.components().insertPrivateProjectDto();
-    db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
-    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
-    db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
-
-    QualityGateFinder.QualityGateData result = underTest.getEffectiveQualityGate(dbSession, project);
-
-    assertThat(result.getUuid()).isEqualTo(dbQualityGate.getUuid());
-    assertThat(result.isDefault()).isFalse();
-  }
-
-  @Test
-  public void fail_when_default_qgate_defined_does_not_exist() {
-    ProjectDto project = db.components().insertPrivateProjectDto();
-    QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(qg -> qg.setName("Sonar way"));
-    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
-    db.commit();
-
-    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Default quality gate is missing");
-  }
-
-  @Test
-  public void fail_when_project_qgate_defined_does_not_exist() {
-    ProjectDto project = db.components().insertPrivateProjectDto();
-    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
-    db.qualityGates().setDefaultQualityGate(dbQualityGate);
-    db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
-    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
-
-    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Default quality gate is missing");
-  }
-
-  @Test
-  public void fail_when_qgate_property_does_not_exist() {
-    ProjectDto project = db.components().insertPrivateProjectDto();
-    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
-    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
-
-    assertThatThrownBy(() -> underTest.getEffectiveQualityGate(dbSession, project))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Default quality gate is missing");
-  }
-
-  @Test
-  public void fail_when_default_quality_gate_does_not_exists() {
-    QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName("My team QG"));
-    db.qualityGates().setDefaultQualityGate(dbQualityGate);
-    db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
-
-    assertThatThrownBy(() -> underTest.getDefault(dbSession))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Default quality gate is missing");
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexerTest.java
deleted file mode 100644 (file)
index 4144829..0000000
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.qualityprofile.index;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.assertj.core.groups.Tuple;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.es.EsQueueDto;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.qualityprofile.ActiveRuleChange;
-
-import static java.util.Arrays.stream;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
-
-public class ActiveRuleIndexerTest {
-
-  private System2 system2 = System2.INSTANCE;
-
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private ActiveRuleIndexer underTest = new ActiveRuleIndexer(db.getDbClient(), es.client());
-  private RuleDto rule1;
-  private RuleDto rule2;
-  private QProfileDto profile1;
-  private QProfileDto profile2;
-
-  @Before
-  public void before() {
-    rule1 = db.rules().insert();
-    rule2 = db.rules().insert();
-    profile1 = db.qualityProfiles().insert();
-    profile2 = db.qualityProfiles().insert();
-  }
-
-  @Test
-  public void getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ACTIVE_RULE);
-  }
-
-  @Test
-  public void indexOnStartup_does_nothing_if_no_data() {
-    underTest.indexOnStartup(emptySet());
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
-  }
-
-  @Test
-  public void indexOnStartup_indexes_all_data() {
-    ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile1, rule1);
-
-    underTest.indexOnStartup(emptySet());
-
-    List<ActiveRuleDoc> docs = es.getDocuments(TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
-    assertThat(docs).hasSize(1);
-    verify(docs.get(0), profile1, activeRule);
-    assertThatEsQueueTableIsEmpty();
-  }
-
-  @Test
-  public void indexAll_indexes_all_data() {
-    ActiveRuleDto activeRule = db.qualityProfiles().activateRule(profile1, rule1);
-
-    underTest.indexAll();
-
-    List<ActiveRuleDoc> docs = es.getDocuments(TYPE_ACTIVE_RULE, ActiveRuleDoc.class);
-    assertThat(docs).hasSize(1);
-    verify(docs.get(0), profile1, activeRule);
-    assertThatEsQueueTableIsEmpty();
-  }
-
-  @Test
-  public void test_commitAndIndex() {
-    ActiveRuleDto ar1 = db.qualityProfiles().activateRule(profile1, rule1);
-    ActiveRuleDto ar2 = db.qualityProfiles().activateRule(profile2, rule1);
-    db.qualityProfiles().activateRule(profile2, rule2);
-
-    commitAndIndex(rule1, ar1, ar2);
-
-    verifyOnlyIndexed(ar1, ar2);
-    assertThatEsQueueTableIsEmpty();
-  }
-
-  @Test
-  public void commitAndIndex_empty_list() {
-    db.qualityProfiles().activateRule(profile1, rule1);
-
-    underTest.commitAndIndex(db.getSession(), Collections.emptyList());
-
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
-    assertThatEsQueueTableIsEmpty();
-  }
-
-  @Test
-  public void commitAndIndex_keeps_elements_to_recover_in_ES_QUEUE_on_errors() {
-    ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
-    es.lockWrites(TYPE_ACTIVE_RULE);
-
-    commitAndIndex(rule1, ar);
-
-    EsQueueDto expectedItem = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "ar_" + ar.getUuid(), "activeRuleUuid", ar.getRuleUuid());
-    assertThatEsQueueContainsExactly(expectedItem);
-    es.unlockWrites(TYPE_ACTIVE_RULE);
-  }
-
-  @Test
-  public void commitAndIndex_deletes_the_documents_that_dont_exist_in_database() {
-    ActiveRuleDto ar = db.qualityProfiles().activateRule(profile1, rule1);
-    indexAll();
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
-
-    db.getDbClient().activeRuleDao().delete(db.getSession(), ar.getKey());
-    commitAndIndex(rule1, ar);
-
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
-    assertThatEsQueueTableIsEmpty();
-  }
-
-  @Test
-  public void index_fails_and_deletes_doc_if_docIdType_is_unsupported() {
-    EsQueueDto item = EsQueueDto.create(TYPE_ACTIVE_RULE.format(), "the_id", "unsupported", "the_routing");
-    db.getDbClient().esQueueDao().insert(db.getSession(), item);
-
-    underTest.index(db.getSession(), singletonList(item));
-
-    assertThatEsQueueTableIsEmpty();
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isZero();
-  }
-
-  @Test
-  public void commitDeletionOfProfiles() {
-    ActiveRuleDto ar1 = db.qualityProfiles().activateRule(profile1, rule1);
-    db.qualityProfiles().activateRule(profile2, rule1);
-    db.qualityProfiles().activateRule(profile2, rule2);
-    indexAll();
-    db.getDbClient().qualityProfileDao().deleteRulesProfilesByUuids(db.getSession(), singletonList(profile2.getRulesProfileUuid()));
-
-    underTest.commitDeletionOfProfiles(db.getSession(), singletonList(profile2));
-
-    verifyOnlyIndexed(ar1);
-  }
-
-  @Test
-  public void commitDeletionOfProfiles_does_nothing_if_profiles_are_not_indexed() {
-    db.qualityProfiles().activateRule(profile1, rule1);
-    indexAll();
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
-
-    underTest.commitDeletionOfProfiles(db.getSession(), singletonList(profile2));
-
-    assertThat(es.countDocuments(TYPE_ACTIVE_RULE)).isOne();
-  }
-
-  private void assertThatEsQueueTableIsEmpty() {
-    assertThat(db.countRowsOfTable(db.getSession(), "es_queue")).isZero();
-  }
-
-  private void assertThatEsQueueContainsExactly(EsQueueDto expected) {
-    Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), system2.now() + 1_000, 10);
-    assertThat(items)
-      .extracting(EsQueueDto::getDocId, EsQueueDto::getDocIdType, EsQueueDto::getDocRouting)
-      .containsExactlyInAnyOrder(Tuple.tuple(expected.getDocId(), expected.getDocIdType(), expected.getDocRouting()));
-  }
-
-  private void commitAndIndex(RuleDto rule, ActiveRuleDto... ar) {
-    underTest.commitAndIndex(db.getSession(), stream(ar)
-      .map(a -> new ActiveRuleChange(ActiveRuleChange.Type.ACTIVATED, a, rule))
-      .collect(Collectors.toList()));
-  }
-
-  private void verifyOnlyIndexed(ActiveRuleDto... expected) {
-    List<String> docs = es.getIds(TYPE_ACTIVE_RULE);
-    assertThat(docs).hasSize(expected.length);
-    for (ActiveRuleDto activeRuleDto : expected) {
-      assertThat(docs).contains("ar_" + activeRuleDto.getUuid());
-    }
-  }
-
-  private void verify(ActiveRuleDoc doc1, QProfileDto profile, ActiveRuleDto activeRule) {
-    assertThat(doc1)
-      .matches(doc -> doc.getId().equals("ar_" + activeRule.getUuid()))
-      .matches(doc -> doc.getRuleProfileUuid().equals(profile.getRulesProfileUuid()))
-      .matches(doc -> doc.getSeverity().equals(activeRule.getSeverityString()));
-  }
-
-  private void indexAll() {
-    underTest.indexOnStartup(emptySet());
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/DefaultRuleFinderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/DefaultRuleFinderTest.java
deleted file mode 100644 (file)
index 92e97d8..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.rule;
-
-import java.util.Set;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Scope;
-
-import static java.util.Collections.emptySet;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class DefaultRuleFinderTest {
-
-  @org.junit.Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession session = dbTester.getSession();
-
-  private final RuleDto rule1 = new RuleDto()
-    .setName("Check Header")
-    .setConfigKey("Checker/Treewalker/HeaderCheck")
-    .setRuleKey("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck")
-    .setRepositoryKey("checkstyle")
-    .setSeverity(4)
-    .setScope(Scope.MAIN)
-    .setStatus(RuleStatus.READY);
-
-  private final RuleDto rule2 = new RuleDto()
-    .setName("Disabled checked")
-    .setConfigKey("Checker/Treewalker/DisabledCheck")
-    .setRuleKey("DisabledCheck")
-    .setRepositoryKey("checkstyle")
-    .setSeverity(4)
-    .setScope(Scope.MAIN)
-    .setStatus(RuleStatus.REMOVED);
-
-  private final RuleDto rule3 = new RuleDto()
-    .setName("Check Annotation")
-    .setConfigKey("Checker/Treewalker/AnnotationUseStyleCheck")
-    .setRuleKey("com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck")
-    .setRepositoryKey("checkstyle")
-    .setSeverity(4)
-    .setScope(Scope.MAIN)
-    .setStatus(RuleStatus.READY);
-
-  private final RuleDto rule4 = new RuleDto()
-    .setName("Call Super First")
-    .setConfigKey("rulesets/android.xml/CallSuperFirst")
-    .setRuleKey("CallSuperFirst")
-    .setRepositoryKey("pmd")
-    .setSeverity(2)
-    .setScope(Scope.MAIN)
-    .setStatus(RuleStatus.READY);
-
-  private final DefaultRuleFinder underTest = new DefaultRuleFinder(dbClient, mock(RuleDescriptionFormatter.class));
-
-  @Before
-  public void setup() {
-    dbTester.rules().insert(rule1);
-    dbTester.rules().insert(rule2);
-    dbTester.rules().insert(rule3);
-    dbTester.rules().insert(rule4);
-    session.commit();
-  }
-
-  @Test
-  public void should_success_finder_wrap() {
-    // should_find_by_key
-    Rule rule = underTest.findByKey("checkstyle", "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck");
-    assertThat(rule).isNotNull();
-    assertThat(rule.getKey()).isEqualTo(("com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck"));
-    assertThat(rule.isEnabled()).isTrue();
-
-    // find_should_return_null_if_no_results
-    assertThat(underTest.findByKey("checkstyle", "unknown")).isNull();
-    assertThat(underTest.find(RuleQuery.create().withRepositoryKey("checkstyle").withConfigKey("unknown"))).isNull();
-
-    // find_repository_rules
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey("checkstyle"))).hasSize(2);
-
-    // find_all_enabled
-    assertThat(underTest.findAll(RuleQuery.create())).extracting(Rule::ruleKey).containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
-
-    // find_all
-    assertThat(underTest.findAll()).extracting(RuleDto::getRuleKey).containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
-
-    // do_not_find_disabled_rules
-    assertThat(underTest.findByKey("checkstyle", "DisabledCheck")).isNull();
-
-    // do_not_find_unknown_rules
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey("unknown_repository"))).isEmpty();
-
-    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "CallSuperFirst")).get().getUuid()).isEqualTo(rule4.getUuid());
-    assertThat(underTest.findDtoByUuid(rule4.getUuid())).isPresent();
-  }
-
-  @Test
-  public void should_fail_find() {
-    assertThat(underTest.findDtoByKey(RuleKey.of("pmd", "unknown"))).isEmpty();
-    assertThat(underTest.findDtoByUuid("unknown")).isEmpty();
-  }
-
-  @Test
-  public void find_all_not_include_removed_rule() {
-    // rule 3 is REMOVED
-    assertThat(underTest.findAll(RuleQuery.create())).extracting(Rule::ruleKey).containsOnly(rule1.getKey(), rule3.getKey(), rule4.getKey());
-    assertThat(underTest.findAll()).extracting(RuleDto::getRuleKey).containsOnly(rule1.getKey().rule(), rule3.getKey().rule(), rule4.getKey().rule());
-  }
-
-  @Test
-  public void findByKey_populates_system_tags_but_not_tags() {
-    RuleDto ruleDto = dbTester.rules()
-      .insert(t -> t.setSystemTags(Set.of(randomAlphanumeric(5), randomAlphanumeric(6))).setTags(emptySet()));
-    dbTester.rules().insertRule();
-
-    Rule rule = underTest.findByKey(ruleDto.getKey());
-    assertThat(rule.getSystemTags())
-      .containsOnlyElementsOf(ruleDto.getSystemTags());
-    assertThat(rule.getTags()).isEmpty();
-
-    rule = underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey());
-    assertThat(rule.getSystemTags())
-      .containsOnlyElementsOf(ruleDto.getSystemTags());
-    assertThat(rule.getTags()).isEmpty();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexDefinitionTest.java
deleted file mode 100644 (file)
index 36ccc3a..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.rule.index;
-
-import com.google.common.collect.ImmutableMap;
-import java.io.IOException;
-import java.util.List;
-import org.apache.commons.lang.StringUtils;
-import org.apache.lucene.search.TotalHits;
-import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.indices.AnalyzeRequest;
-import org.elasticsearch.client.indices.AnalyzeResponse;
-import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Index;
-import org.sonar.server.es.IndexDefinition;
-import org.sonar.server.es.IndexType;
-import org.sonar.server.es.newindex.NewIndex;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
-import static org.sonar.process.ProcessProperties.Property.CLUSTER_ENABLED;
-import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
-import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
-import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
-import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_REPOSITORY;
-import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_UUID;
-import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
-
-public class RuleIndexDefinitionTest {
-
-  private MapSettings settings = new MapSettings();
-  private RuleIndexDefinition underTest = new RuleIndexDefinition(settings.asConfig());
-
-  @Rule
-  public EsTester tester = EsTester.create();
-
-  @Test
-  public void test_definition_of_index() {
-    IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
-    underTest.define(context);
-
-    assertThat(context.getIndices()).hasSize(1);
-    NewIndex<?> ruleIndex = context.getIndices().get("rules");
-    assertThat(ruleIndex.getMainType())
-      .isEqualTo(IndexType.main(Index.withRelations("rules"), "rule"));
-    assertThat(ruleIndex.getRelationsStream())
-      .extracting(IndexType.IndexRelationType::getName)
-      .containsOnly("activeRule");
-
-    // no cluster by default
-    assertThat(ruleIndex.getSetting("index.number_of_shards")).isEqualTo("2");
-    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("0");
-  }
-
-  @Test
-  public void enable_replica_if_clustering_is_enabled() {
-    settings.setProperty(CLUSTER_ENABLED.getKey(), true);
-    IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
-    underTest.define(context);
-
-    NewIndex ruleIndex = context.getIndices().get("rules");
-    assertThat(ruleIndex.getSetting("index.number_of_replicas")).isEqualTo("1");
-  }
-
-  @Test
-  public void support_long_html_description() {
-    String longText = StringUtils.repeat("The quick brown fox jumps over the lazy dog ", 700);
-
-    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(longText);
-    assertThat(tokens).extracting(AnalyzeResponse.AnalyzeToken::getTerm).containsOnly(
-      "quick", "brown", "fox", "jump", "over", "lazi", "dog");
-
-    // the following method fails if PUT fails
-    tester.putDocuments(TYPE_RULE, new RuleDoc(ImmutableMap.of(
-      FIELD_RULE_UUID, "123",
-      FIELD_RULE_HTML_DESCRIPTION, longText,
-      FIELD_RULE_REPOSITORY, "java",
-      FIELD_RULE_KEY, "java:S001")));
-    assertThat(tester.countDocuments(TYPE_RULE)).isOne();
-    assertThat(tester.client().search(EsClient.prepareSearch(TYPE_RULE)
-      .source(new SearchSourceBuilder()
-        .query(matchQuery(ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), "brown fox jumps lazy"))))
-      .getHits().getTotalHits()).isEqualTo(new TotalHits(1, TotalHits.Relation.EQUAL_TO));
-  }
-
-  @Test
-  public void remove_html_characters_of_html_description() {
-    String text = "<p>html <i>line</i></p>";
-    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(text);
-
-    assertThat(tokens).extracting("term").containsOnly("html", "line");
-  }
-
-  @Test
-  public void sanitize_html_description_as_it_is_english() {
-    String text = "this is a small list of words";
-    // "this", "is", "a" and "of" are not indexed.
-    // Plural "words" is converted to singular "word"
-    List<AnalyzeResponse.AnalyzeToken> tokens = analyzeIndexedTokens(text);
-    assertThat(tokens).extracting("term").containsOnly("small", "list", "word");
-  }
-
-  private List<AnalyzeResponse.AnalyzeToken> analyzeIndexedTokens(String text) {
-    try {
-      return tester.nativeClient().indices()
-        .analyze(AnalyzeRequest.withField(TYPE_RULE.getIndex().getName(), ENGLISH_HTML_ANALYZER.subField(FIELD_RULE_HTML_DESCRIPTION), text), RequestOptions.DEFAULT)
-        .getTokens();
-    } catch (IOException e) {
-      throw new IllegalStateException("Could not analyze indexed tokens for text: " + text);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java
deleted file mode 100644 (file)
index 7c8f66c..0000000
+++ /dev/null
@@ -1,1119 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.rule.index;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchIdResult;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.security.SecurityStandards;
-
-import static com.google.common.collect.ImmutableSet.of;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.singletonList;
-import static java.util.stream.IntStream.rangeClosed;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions.entry;
-import static org.junit.Assert.fail;
-import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.api.rule.Severity.CRITICAL;
-import static org.sonar.api.rule.Severity.INFO;
-import static org.sonar.api.rule.Severity.MAJOR;
-import static org.sonar.api.rule.Severity.MINOR;
-import static org.sonar.api.rules.RuleType.BUG;
-import static org.sonar.api.rules.RuleType.CODE_SMELL;
-import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
-import static org.sonar.api.rules.RuleType.VULNERABILITY;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
-import static org.sonar.db.rule.RuleTesting.newRule;
-import static org.sonar.db.rule.RuleTesting.setCreatedAt;
-import static org.sonar.db.rule.RuleTesting.setIsExternal;
-import static org.sonar.db.rule.RuleTesting.setIsTemplate;
-import static org.sonar.db.rule.RuleTesting.setLanguage;
-import static org.sonar.db.rule.RuleTesting.setName;
-import static org.sonar.db.rule.RuleTesting.setRepositoryKey;
-import static org.sonar.db.rule.RuleTesting.setRuleKey;
-import static org.sonar.db.rule.RuleTesting.setSecurityStandards;
-import static org.sonar.db.rule.RuleTesting.setSeverity;
-import static org.sonar.db.rule.RuleTesting.setStatus;
-import static org.sonar.db.rule.RuleTesting.setSystemTags;
-import static org.sonar.db.rule.RuleTesting.setTags;
-import static org.sonar.db.rule.RuleTesting.setTemplateId;
-import static org.sonar.db.rule.RuleTesting.setType;
-import static org.sonar.db.rule.RuleTesting.setUpdatedAt;
-import static org.sonar.server.qualityprofile.ActiveRuleInheritance.INHERITED;
-import static org.sonar.server.qualityprofile.ActiveRuleInheritance.OVERRIDES;
-import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
-import static org.sonar.server.rule.index.RuleIndex.FACET_REPOSITORIES;
-import static org.sonar.server.rule.index.RuleIndex.FACET_TAGS;
-import static org.sonar.server.rule.index.RuleIndex.FACET_TYPES;
-import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
-import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
-import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_INSECURE_INTERACTION;
-import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_RISKY_RESOURCE;
-
-public class RuleIndexTest {
-
-  private final System2 system2 = new AlwaysIncreasingSystem2();
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient());
-  private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client());
-
-  private final RuleIndex underTest = new RuleIndex(es.client(), system2);
-  private final UuidFactory uuidFactory = UuidFactoryFast.getInstance();
-
-  @Test
-  public void search_all_rules() {
-    createRule();
-    createRule();
-    index();
-
-    SearchIdResult<String> results = underTest.search(new RuleQuery(), new SearchOptions());
-
-    assertThat(results.getTotal()).isEqualTo(2);
-    assertThat(results.getUuids()).hasSize(2);
-  }
-
-  @Test
-  public void search_by_key() {
-    RuleDto js1 = createRule(
-      setRepositoryKey("javascript"),
-      setRuleKey("X001"));
-    RuleDto cobol1 = createRule(
-      setRepositoryKey("cobol"),
-      setRuleKey("X001"));
-    createRule(
-      setRepositoryKey("php"),
-      setRuleKey("S002"));
-    index();
-
-    // key
-    RuleQuery query = new RuleQuery().setQueryText("X001");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(js1.getUuid(), cobol1.getUuid());
-
-    // partial key does not match
-    query = new RuleQuery().setQueryText("X00");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // repo:key -> nice-to-have !
-    query = new RuleQuery().setQueryText("javascript:X001");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(js1.getUuid());
-  }
-
-  @Test
-  public void search_by_case_insensitive_key() {
-    RuleDto ruleDto = createRule(
-      setRepositoryKey("javascript"),
-      setRuleKey("X001"));
-    index();
-
-    RuleQuery query = new RuleQuery().setQueryText("x001");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(ruleDto.getUuid());
-  }
-
-  @Test
-  public void filter_by_key() {
-    createRule(
-      setRepositoryKey("javascript"),
-      setRuleKey("X001"));
-    createRule(
-      setRepositoryKey("cobol"),
-      setRuleKey("X001"));
-    createRule(
-      setRepositoryKey("php"),
-      setRuleKey("S002"));
-    index();
-
-    // key
-    RuleQuery query = new RuleQuery().setKey(RuleKey.of("javascript", "X001").toString());
-
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
-
-    // partial key does not match
-    query = new RuleQuery().setKey("X001");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-  }
-
-  @Test
-  public void search_name_by_query() {
-    createRule(setName("testing the partial match and matching of rule"));
-    index();
-
-    // substring
-    RuleQuery query = new RuleQuery().setQueryText("test");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
-
-    // substring
-    query = new RuleQuery().setQueryText("partial match");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
-
-    // case-insensitive
-    query = new RuleQuery().setQueryText("TESTING");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(1);
-
-    // not found
-    query = new RuleQuery().setQueryText("not present");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-  }
-
-  @Test
-  public void search_name_with_protected_chars() {
-    RuleDto rule = createRule(setName("ja#va&sc\"r:ipt"));
-    index();
-
-    RuleQuery protectedCharsQuery = new RuleQuery().setQueryText(rule.getName());
-    List<String> results = underTest.search(protectedCharsQuery, new SearchOptions()).getUuids();
-    assertThat(results).containsOnly(rule.getUuid());
-  }
-
-  @Test
-  public void search_content_by_query() {
-    // it's important to set all the fields being used by the search (name, desc, key, lang, ...),
-    // otherwise the generated random values may raise false-positives
-    RuleDto rule1 = insertJavaRule("My great rule CWE-123 which makes your code 1000 times better!", "123", "rule 123");
-    RuleDto rule2 = insertJavaRule("Another great and shiny rule CWE-124", "124", "rule 124");
-    RuleDto rule3 = insertJavaRule("Another great rule CWE-1000", "1000", "rule 1000");
-    RuleDto rule4 = insertJavaRule("<h1>HTML-Geeks</h1><p style=\"color:blue\">special formatting!</p><table><tr><td>inside</td><td>tables</td></tr></table>", "404", "rule 404");
-    RuleDto rule5 = insertJavaRule("internationalization missunderstandings alsdkjfnadklsjfnadkdfnsksdjfn", "405", "rule 405");
-    index();
-
-    // partial match at word boundary
-    assertThat(underTest.search(new RuleQuery().setQueryText("CWE"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(), rule3.getUuid());
-
-    // full match
-    assertThat(underTest.search(new RuleQuery().setQueryText("CWE-123"), new SearchOptions()).getUuids()).containsExactly(rule1.getUuid());
-
-    // match somewhere else in the text
-    assertThat(underTest.search(new RuleQuery().setQueryText("CWE-1000"), new SearchOptions()).getUuids()).containsExactly(rule3.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("CWE 1000"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule3.getUuid(), rule1.getUuid());
-
-    // several words
-    assertThat(underTest.search(new RuleQuery().setQueryText("great rule"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule1.getUuid(), rule2.getUuid(),
-      rule3.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("rule Another"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule2.getUuid(), rule3.getUuid());
-
-    // no matches
-    assertThat(underTest.search(new RuleQuery().setQueryText("unexisting"), new SearchOptions()).getUuids()).isEmpty();
-    assertThat(underTest.search(new RuleQuery().setQueryText("great rule unexisting"), new SearchOptions()).getUuids()).isEmpty();
-
-    // stopwords
-    assertThat(underTest.search(new RuleQuery().setQueryText("and"), new SearchOptions()).getUuids()).isEmpty();
-    assertThat(underTest.search(new RuleQuery().setQueryText("great and shiny"), new SearchOptions()).getUuids()).isEmpty();
-
-    // html
-    assertThat(underTest.search(new RuleQuery().setQueryText("h1"), new SearchOptions()).getUuids()).isEmpty();
-    assertThat(underTest.search(new RuleQuery().setQueryText("style"), new SearchOptions()).getUuids()).isEmpty();
-    assertThat(underTest.search(new RuleQuery().setQueryText("special"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule4.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("geeks formatting inside tables"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule4.getUuid());
-
-    // long words
-    assertThat(underTest.search(new RuleQuery().setQueryText("missunderstand"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("missunderstandings"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("alsdkjfnadklsjfnadkdfnsksdjfn"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("internationalization"), new SearchOptions()).getUuids()).containsExactlyInAnyOrder(rule5.getUuid());
-    assertThat(underTest.search(new RuleQuery().setQueryText("internationalizationBlaBla"), new SearchOptions()).getUuids()).isEmpty();
-  }
-
-  private RuleDto insertJavaRule(String description, String ruleKey, String name) {
-    RuleDto javaRule = newRule(createDefaultRuleDescriptionSection(uuidFactory.create(), description))
-      .setLanguage("java")
-      .setRuleKey(ruleKey)
-      .setName(name);
-    return db.rules().insert(javaRule);
-  }
-
-  @Test
-  public void search_by_any_of_repositories() {
-    RuleDto findbugs = createRule(
-      setRepositoryKey("findbugs"),
-      setRuleKey("S001"));
-    RuleDto pmd = createRule(
-      setRepositoryKey("pmd"),
-      setRuleKey("S002"));
-    index();
-
-    RuleQuery query = new RuleQuery().setRepositories(asList("checkstyle", "pmd"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsExactly(pmd.getUuid());
-
-    // no results
-    query = new RuleQuery().setRepositories(singletonList("checkstyle"));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // empty list => no filter
-    query = new RuleQuery().setRepositories(emptyList());
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(findbugs.getUuid(), pmd.getUuid());
-  }
-
-  @Test
-  public void filter_by_tags() {
-    RuleDto rule1 = createRule(setSystemTags("tag1s"), setTags("tag1"));
-    RuleDto rule2 = createRule(setSystemTags("tag2s"), setTags("tag2"));
-    index();
-
-    assertThat(es.countDocuments(TYPE_RULE)).isEqualTo(2);
-    // tag2s in filter
-    RuleQuery query = new RuleQuery().setTags(of("tag2s"));
-    verifySearch(query, rule2);
-
-    // tag2 in filter
-    query = new RuleQuery().setTags(of("tag2"));
-    verifySearch(query, rule2);
-
-    // empty list => no filter
-    query = new RuleQuery().setTags(emptySet());
-    verifySearch(query, rule1, rule2);
-
-    // null list => no filter
-    query = new RuleQuery().setTags(null);
-    verifySearch(query, rule1, rule2);
-  }
-
-  @Test
-  public void tags_facet_supports_selected_value_with_regexp_special_characters() {
-    createRule(r -> r.setTags(Set.of("misra++")));
-    index();
-
-    RuleQuery query = new RuleQuery()
-      .setTags(singletonList("misra["));
-    SearchOptions options = new SearchOptions().addFacets(FACET_TAGS);
-
-    // do not fail
-    assertThat(underTest.search(query, options).getTotal()).isZero();
-  }
-
-  @Test
-  public void search_by_types() {
-    createRule(setType(CODE_SMELL));
-    RuleDto vulnerability = createRule(setType(VULNERABILITY));
-    RuleDto bug1 = createRule(setType(BUG));
-    RuleDto bug2 = createRule(setType(BUG));
-    index();
-
-    // find all
-    RuleQuery query = new RuleQuery();
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
-
-    // type3 in filter
-    query = new RuleQuery().setTypes(of(VULNERABILITY));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(vulnerability.getUuid());
-
-    query = new RuleQuery().setTypes(of(BUG));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).containsOnly(bug1.getUuid(), bug2.getUuid());
-
-    // types in query => nothing
-    query = new RuleQuery().setQueryText("code smell bug vulnerability");
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // null list => no filter
-    query = new RuleQuery().setTypes(emptySet());
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
-
-    // null list => no filter
-    query = new RuleQuery().setTypes(null);
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(4);
-  }
-
-  @Test
-  public void search_by_is_template() {
-    RuleDto ruleNoTemplate = createRule(setIsTemplate(false));
-    RuleDto ruleIsTemplate = createRule(setIsTemplate(true));
-    index();
-
-    // find all
-    RuleQuery query = new RuleQuery();
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).hasSize(2);
-
-    // Only template
-    query = new RuleQuery().setIsTemplate(true);
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ruleIsTemplate.getUuid());
-
-    // Only not template
-    query = new RuleQuery().setIsTemplate(false);
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ruleNoTemplate.getUuid());
-
-    // null => no filter
-    query = new RuleQuery().setIsTemplate(null);
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ruleIsTemplate.getUuid(), ruleNoTemplate.getUuid());
-  }
-
-  @Test
-  public void search_by_is_external() {
-    RuleDto ruleIsNotExternal = createRule(setIsExternal(false));
-    RuleDto ruleIsExternal = createRule(setIsExternal(true));
-    index();
-
-    // Only external
-    RuleQuery query = new RuleQuery().setIncludeExternal(true);
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ruleIsExternal.getUuid(), ruleIsNotExternal.getUuid());
-
-    // Only not external
-    query = new RuleQuery().setIncludeExternal(false);
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ruleIsNotExternal.getUuid());
-  }
-
-  @Test
-  public void search_by_template_key() {
-    RuleDto template = createRule(setIsTemplate(true));
-    RuleDto customRule = createRule(setTemplateId(template.getUuid()));
-    index();
-
-    // find all
-    RuleQuery query = new RuleQuery();
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).hasSize(2);
-
-    // Only custom rule
-    query = new RuleQuery().setTemplateKey(template.getKey().toString());
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(customRule.getUuid());
-
-    // null => no filter
-    query = new RuleQuery().setTemplateKey(null);
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-  }
-
-  @Test
-  public void search_by_any_of_languages() {
-    createRule(setLanguage("java"));
-    RuleDto javascript = createRule(setLanguage("js"));
-    index();
-
-    RuleQuery query = new RuleQuery().setLanguages(asList("cobol", "js"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(javascript.getUuid());
-
-    // no results
-    query = new RuleQuery().setLanguages(singletonList("cpp"));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // empty list => no filter
-    query = new RuleQuery().setLanguages(emptyList());
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-
-    // null list => no filter
-    query = new RuleQuery().setLanguages(null);
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-  }
-
-  @Test
-  public void search_by_security_cwe_return_vulnerabilities_and_hotspots_only() {
-    RuleDto rule1 = createRule(setSecurityStandards(of("cwe:543", "cwe:123", "owaspTop10:a1")), r -> r.setType(VULNERABILITY));
-    RuleDto rule2 = createRule(setSecurityStandards(of("cwe:543", "owaspTop10:a1")), r -> r.setType(SECURITY_HOTSPOT));
-    createRule(setSecurityStandards(of("owaspTop10:a1")), r -> r.setType(CODE_SMELL));
-    index();
-
-    RuleQuery query = new RuleQuery().setCwe(of("543"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("cwe"));
-    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
-  }
-
-  @Test
-  public void search_by_security_owaspTop10_2017_return_vulnerabilities_and_hotspots_only() {
-    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:543")), r -> r.setType(VULNERABILITY));
-    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT));
-    createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL));
-    index();
-
-    RuleQuery query = new RuleQuery().setOwaspTop10(of("a5", "a10"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("owaspTop10"));
-    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
-  }
-
-  @Test
-  public void search_by_security_owaspTop10_2021_return_vulnerabilities_and_hotspots_only() {
-    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10-2021:a1", "owaspTop10-2021:a10", "cwe:543")), r -> r.setType(VULNERABILITY));
-    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10-2021:a10", "cwe:543")), r -> r.setType(SECURITY_HOTSPOT));
-    createRule(setSecurityStandards(of("cwe:543")), r -> r.setType(CODE_SMELL));
-    index();
-
-    RuleQuery query = new RuleQuery().setOwaspTop10For2021(of("a5", "a10"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("owaspTop10-2021"));
-    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
-  }
-
-  @Test
-  public void search_by_security_sansTop25_return_vulnerabilities_and_hotspots_only() {
-    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10:a10", "cwe:89")), r -> r.setType(VULNERABILITY));
-    RuleDto rule2 = createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(SECURITY_HOTSPOT));
-    createRule(setSecurityStandards(of("cwe:306")), r -> r.setType(CODE_SMELL));
-    index();
-
-    RuleQuery query = new RuleQuery().setSansTop25(of(SANS_TOP_25_INSECURE_INTERACTION, SANS_TOP_25_RISKY_RESOURCE));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sansTop25"));
-    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule2.getUuid());
-  }
-
-  @Test
-  public void search_by_security_sonarsource_return_vulnerabilities_and_hotspots_only() {
-    RuleDto rule1 = createRule(setSecurityStandards(of("owaspTop10:a1", "owaspTop10-2021:a10", "cwe:89")), r -> r.setType(VULNERABILITY));
-    createRule(setSecurityStandards(of("owaspTop10:a10", "cwe:829")), r -> r.setType(CODE_SMELL));
-    RuleDto rule3 = createRule(setSecurityStandards(of("cwe:601")), r -> r.setType(SECURITY_HOTSPOT));
-    index();
-
-    RuleQuery query = new RuleQuery().setSonarsourceSecurity(of("sql-injection", "open-redirect"));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sonarsourceSecurity"));
-    assertThat(results.getUuids()).containsOnly(rule1.getUuid(), rule3.getUuid());
-  }
-
-  @Test
-  public void search_by_security_sonarsource_return_complete_list_of_facets() {
-
-    List<RuleDto> rules = new ArrayList<>();
-
-    //Creation of one rule for each standard security category defined (except other)
-    for (Map.Entry<SecurityStandards.SQCategory, Set<String>> sqCategorySetEntry : SecurityStandards.CWES_BY_SQ_CATEGORY.entrySet()) {
-      rules.add(createRule(setSecurityStandards(of("cwe:" + sqCategorySetEntry.getValue().iterator().next())), r -> r.setType(SECURITY_HOTSPOT)));
-    }
-    index();
-
-    RuleQuery query = new RuleQuery();
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions().addFacets("sonarsourceSecurity"));
-
-    assertThat(results.getFacets().get("sonarsourceSecurity"))
-      .as("It should have as many facets returned as there are rules defined, and it is not truncated")
-      .hasSize(rules.size());
-  }
-
-  @Test
-  public void compare_to_another_profile() {
-    String xoo = "xoo";
-    QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(xoo));
-    QProfileDto anotherProfile = db.qualityProfiles().insert(p -> p.setLanguage(xoo));
-    RuleDto commonRule = db.rules().insertRule(r -> r.setLanguage(xoo));
-    RuleDto profileRule1 = db.rules().insertRule(r -> r.setLanguage(xoo));
-    RuleDto profileRule2 = db.rules().insertRule(r -> r.setLanguage(xoo));
-    RuleDto profileRule3 = db.rules().insertRule(r -> r.setLanguage(xoo));
-    RuleDto anotherProfileRule1 = db.rules().insertRule(r -> r.setLanguage(xoo));
-    RuleDto anotherProfileRule2 = db.rules().insertRule(r -> r.setLanguage(xoo));
-    db.qualityProfiles().activateRule(profile, commonRule);
-    db.qualityProfiles().activateRule(profile, profileRule1);
-    db.qualityProfiles().activateRule(profile, profileRule2);
-    db.qualityProfiles().activateRule(profile, profileRule3);
-    db.qualityProfiles().activateRule(anotherProfile, commonRule);
-    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule1);
-    db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2);
-    index();
-
-    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(anotherProfile), anotherProfileRule1, anotherProfileRule2);
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(anotherProfile), commonRule);
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile).setCompareToQProfile(profile), commonRule, profileRule1, profileRule2, profileRule3);
-    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile).setCompareToQProfile(profile));
-  }
-
-  @SafeVarargs
-  private RuleDto createRule(Consumer<RuleDto>... consumers) {
-    return db.rules().insert(consumers);
-  }
-
-  private RuleDto createJavaRule() {
-    return createRule(r -> r.setLanguage("java"));
-  }
-
-  @Test
-  public void search_by_any_of_severities() {
-    createRule(setSeverity(BLOCKER));
-    RuleDto info = createRule(setSeverity(INFO));
-    index();
-
-    RuleQuery query = new RuleQuery().setSeverities(asList(INFO, MINOR));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(info.getUuid());
-
-    // no results
-    query = new RuleQuery().setSeverities(singletonList(MINOR));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // empty list => no filter
-    query = new RuleQuery().setSeverities(emptyList());
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-
-    // null list => no filter
-    query = new RuleQuery().setSeverities();
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-  }
-
-  @Test
-  public void search_by_any_of_statuses() {
-    createRule(setStatus(RuleStatus.BETA));
-    RuleDto ready = createRule(setStatus(RuleStatus.READY));
-    index();
-
-    RuleQuery query = new RuleQuery().setStatuses(asList(RuleStatus.DEPRECATED, RuleStatus.READY));
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsOnly(ready.getUuid());
-
-    // no results
-    query = new RuleQuery().setStatuses(singletonList(RuleStatus.DEPRECATED));
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).isEmpty();
-
-    // empty list => no filter
-    query = new RuleQuery().setStatuses(emptyList());
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-
-    // null list => no filter
-    query = new RuleQuery().setStatuses(null);
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(2);
-  }
-
-  @Test
-  public void activation_parameter_is_ignored_if_profile_is_not_set() {
-    RuleDto rule1 = createJavaRule();
-    RuleDto rule2 = createJavaRule();
-    QProfileDto profile1 = createJavaProfile();
-    db.qualityProfiles().activateRule(profile1, rule1);
-    index();
-
-    // all rules are returned
-    verifySearch(newRuleQuery().setActivation(true), rule1, rule2);
-  }
-
-  @Test
-  public void search_by_activation() {
-    RuleDto rule1 = createJavaRule();
-    RuleDto rule2 = createJavaRule();
-    RuleDto rule3 = createJavaRule();
-    QProfileDto profile1 = createJavaProfile();
-    QProfileDto profile2 = createJavaProfile();
-    db.qualityProfiles().activateRule(profile1, rule1);
-    db.qualityProfiles().activateRule(profile2, rule1);
-    db.qualityProfiles().activateRule(profile1, rule2);
-    index();
-
-    // active rules
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile1), rule1, rule2);
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(profile2), rule1);
-
-    // inactive rules
-    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile1), rule3);
-    verifySearch(newRuleQuery().setActivation(false).setQProfile(profile2), rule2, rule3);
-  }
-
-  private void verifyEmptySearch(RuleQuery query) {
-    verifySearch(query);
-  }
-
-  private void verifySearch(RuleQuery query, RuleDto... expectedRules) {
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions());
-    assertThat(result.getTotal()).isEqualTo(expectedRules.length);
-    assertThat(result.getUuids()).hasSize(expectedRules.length);
-    for (RuleDto expectedRule : expectedRules) {
-      assertThat(result.getUuids()).contains(expectedRule.getUuid());
-    }
-  }
-
-  private void index() {
-    ruleIndexer.indexOnStartup(Sets.newHashSet(TYPE_RULE));
-    activeRuleIndexer.indexOnStartup(Sets.newHashSet(TYPE_ACTIVE_RULE));
-  }
-
-  private RuleQuery newRuleQuery() {
-    return new RuleQuery();
-  }
-
-  private QProfileDto createJavaProfile() {
-    return db.qualityProfiles().insert(p -> p.setLanguage("java"));
-  }
-
-  @Test
-  public void search_by_activation_and_inheritance() {
-    RuleDto rule1 = createJavaRule();
-    RuleDto rule2 = createJavaRule();
-    RuleDto rule3 = createJavaRule();
-    RuleDto rule4 = createJavaRule();
-    QProfileDto parent = createJavaProfile();
-    QProfileDto child = createJavaProfile();
-    db.qualityProfiles().activateRule(parent, rule1);
-    db.qualityProfiles().activateRule(parent, rule2);
-    db.qualityProfiles().activateRule(parent, rule3);
-    db.qualityProfiles().activateRule(child, rule1, ar -> ar.setInheritance(INHERITED.name()));
-    db.qualityProfiles().activateRule(child, rule2, ar -> ar.setInheritance(OVERRIDES.name()));
-    db.qualityProfiles().activateRule(child, rule3, ar -> ar.setInheritance(INHERITED.name()));
-    index();
-
-    // all rules
-    verifySearch(newRuleQuery(), rule1, rule2, rule3, rule4);
-
-    // inherited/overrides rules on parent
-    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(INHERITED.name())));
-    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(OVERRIDES.name())));
-
-    // inherited/overrides rules on child
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name())), rule1, rule3);
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(OVERRIDES.name())), rule2);
-
-    // inherited AND overridden on parent
-    verifyEmptySearch(newRuleQuery().setActivation(true).setQProfile(parent).setInheritance(of(INHERITED.name(), OVERRIDES.name())));
-
-    // inherited AND overridden on child
-    verifySearch(newRuleQuery().setActivation(true).setQProfile(child).setInheritance(of(INHERITED.name(), OVERRIDES.name())), rule1, rule2, rule3);
-  }
-
-  @Test
-  public void search_by_activation_and_severity() {
-    RuleDto major = createRule(setSeverity(MAJOR));
-    RuleDto minor = createRule(setSeverity(MINOR));
-    createRule(setSeverity(INFO));
-    QProfileDto profile1 = createJavaProfile();
-    QProfileDto profile2 = createJavaProfile();
-    db.qualityProfiles().activateRule(profile1, major, ar -> ar.setSeverity(BLOCKER));
-    db.qualityProfiles().activateRule(profile2, major, ar -> ar.setSeverity(BLOCKER));
-    db.qualityProfiles().activateRule(profile1, minor, ar -> ar.setSeverity(CRITICAL));
-    index();
-
-    // count activation severities of all active rules
-    RuleQuery query = newRuleQuery().setActivation(true).setQProfile(profile1);
-    verifySearch(query, major, minor);
-    verifyFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES, entry(BLOCKER, 1L), entry(CRITICAL, 1L));
-
-    // check stickyness of active severity facet
-    query = newRuleQuery().setActivation(true).setQProfile(profile1).setActiveSeverities(singletonList(CRITICAL));
-    verifySearch(query, minor);
-    verifyFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES, entry(BLOCKER, 1L), entry(CRITICAL, 1L));
-  }
-
-  @Test
-  public void facet_by_activation_severity_is_ignored_when_profile_is_not_specified() {
-    RuleDto rule = createJavaRule();
-    QProfileDto profile = createJavaProfile();
-    db.qualityProfiles().activateRule(profile, rule);
-    index();
-
-    RuleQuery query = newRuleQuery();
-    verifyNoFacet(query, RuleIndex.FACET_ACTIVE_SEVERITIES);
-  }
-
-  private void verifyFacet(RuleQuery query, String facet, Map.Entry<String, Long>... expectedBuckets) {
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(facet));
-    assertThat(result.getFacets().get(facet))
-      .containsOnly(expectedBuckets);
-  }
-
-  private void verifyNoFacet(RuleQuery query, String facet) {
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(facet));
-    assertThat(result.getFacets().get(facet)).isNull();
-  }
-
-  @Test
-  public void listTags_should_return_tags() {
-    createRule(setSystemTags("sys1", "sys2"), setTags("tag1"));
-    createRule(setSystemTags(), setTags("tag2"));
-
-    index();
-
-    assertThat(underTest.listTags(null, 10)).containsOnly("tag1", "tag2", "sys1", "sys2");
-  }
-
-  @Test
-  public void fail_to_list_tags_when_size_greater_than_500() {
-    assertThatThrownBy(() -> underTest.listTags(null, 501))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessageContaining("Page size must be lower than or equals to 500");
-  }
-
-  @Test
-  public void available_since() {
-    RuleDto ruleOld = createRule(setCreatedAt(-2_000L));
-    RuleDto ruleOlder = createRule(setCreatedAt(-1_000L));
-    index();
-
-    // 0. find all rules;
-    verifySearch(new RuleQuery(), ruleOld, ruleOlder);
-
-    // 1. find all rules available since a date;
-    RuleQuery availableSinceQuery = new RuleQuery().setAvailableSince(-1000L);
-    verifySearch(availableSinceQuery, ruleOlder);
-
-    // 2. find no new rules since tomorrow.
-    RuleQuery availableSinceNowQuery = new RuleQuery().setAvailableSince(1000L);
-    verifyEmptySearch(availableSinceNowQuery);
-  }
-
-  @Test
-  public void global_facet_on_repositories_and_tags() {
-    createRule(setRepositoryKey("php"), setSystemTags("sysTag"), setTags());
-    createRule(setRepositoryKey("php"), setSystemTags(), setTags("tag1"));
-    createRule(setRepositoryKey("javascript"), setSystemTags(), setTags("tag1", "tag2"));
-    index();
-
-    // should not have any facet!
-    RuleQuery query = new RuleQuery();
-    SearchIdResult result1 = underTest.search(query, new SearchOptions());
-    assertThat(result1.getFacets().getAll()).isEmpty();
-
-    // should not have any facet on non matching query!
-    SearchIdResult result2 = underTest.search(new RuleQuery().setQueryText("aeiou"), new SearchOptions().addFacets(singletonList("repositories")));
-    assertThat(result2.getFacets().getAll()).hasSize(1);
-    assertThat(result2.getFacets().getAll().get("repositories")).isEmpty();
-
-    // Repositories Facet is preset
-    SearchIdResult result3 = underTest.search(query, new SearchOptions().addFacets(asList("repositories", "tags")));
-    assertThat(result3.getFacets()).isNotNull();
-
-    // Verify the value of a given facet
-    Map<String, Long> repoFacets = result3.getFacets().get("repositories");
-    assertThat(repoFacets).containsOnly(entry("php", 2L), entry("javascript", 1L));
-
-    // Check that tag facet has both Tags and SystemTags values
-    Map<String, Long> tagFacets = result3.getFacets().get("tags");
-    assertThat(tagFacets).containsOnly(entry("tag1", 2L), entry("sysTag", 1L), entry("tag2", 1L));
-
-    // Check that there are no other facets
-    assertThat(result3.getFacets().getAll()).hasSize(2);
-  }
-
-  private void setupStickyFacets() {
-    createRule(setRepositoryKey("xoo"), setRuleKey("S001"), setLanguage("java"), setTags(), setSystemTags(), setType(BUG));
-    createRule(setRepositoryKey("xoo"), setRuleKey("S002"), setLanguage("java"), setTags(), setSystemTags(), setType(CODE_SMELL));
-    createRule(setRepositoryKey("xoo"), setRuleKey("S003"), setLanguage("java"), setTags(), setSystemTags("T1", "T2"), setType(CODE_SMELL));
-    createRule(setRepositoryKey("xoo"), setRuleKey("S011"), setLanguage("cobol"), setTags(), setSystemTags(), setType(CODE_SMELL));
-    createRule(setRepositoryKey("xoo"), setRuleKey("S012"), setLanguage("cobol"), setTags(), setSystemTags(), setType(BUG));
-    createRule(setRepositoryKey("foo"), setRuleKey("S013"), setLanguage("cobol"), setTags(), setSystemTags("T3", "T4"),
-      setType(VULNERABILITY));
-    createRule(setRepositoryKey("foo"), setRuleKey("S111"), setLanguage("cpp"), setTags(), setSystemTags(), setType(BUG));
-    createRule(setRepositoryKey("foo"), setRuleKey("S112"), setLanguage("cpp"), setTags(), setSystemTags(), setType(CODE_SMELL));
-    createRule(setRepositoryKey("foo"), setRuleKey("S113"), setLanguage("cpp"), setTags(), setSystemTags("T2", "T3"), setType(CODE_SMELL));
-    index();
-  }
-
-  @Test
-  public void sticky_facets_base() {
-    setupStickyFacets();
-
-    RuleQuery query = new RuleQuery();
-
-    assertThat(underTest.search(query, new SearchOptions()).getUuids()).hasSize(9);
-  }
-
-  /**
-   * Facet with no filters at all
-   */
-  @Test
-  public void sticky_facets_no_filters() {
-    setupStickyFacets();
-    RuleQuery query = new RuleQuery();
-
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES,
-      FACET_TAGS, FACET_TYPES)));
-    Map<String, LinkedHashMap<String, Long>> facets = result.getFacets().getAll();
-    assertThat(facets).hasSize(4);
-    assertThat(facets.get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java", "cobol");
-    assertThat(facets.get(FACET_REPOSITORIES).keySet()).containsExactly("xoo", "foo");
-    assertThat(facets.get(FACET_TAGS)).containsOnlyKeys("T1", "T2", "T3", "T4");
-    assertThat(facets.get(FACET_TYPES)).containsOnlyKeys("BUG", "CODE_SMELL", "VULNERABILITY");
-  }
-
-  /**
-   * Facet with a language filter
-   * -- lang facet should still have all language
-   */
-  @Test
-  public void sticky_facets_with_1_filter() {
-    setupStickyFacets();
-    RuleQuery query = new RuleQuery().setLanguages(ImmutableList.of("cpp"));
-
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS)));
-    assertThat(result.getUuids()).hasSize(3);
-    assertThat(result.getFacets().getAll()).hasSize(3);
-    assertThat(result.getFacets().get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java", "cobol");
-    assertThat(result.getFacets().get(FACET_REPOSITORIES)).containsOnlyKeys("foo");
-    assertThat(result.getFacets().get(FACET_TAGS)).containsOnlyKeys("T2", "T3");
-  }
-
-  @Test
-  public void languages_facet_should_return_top_100_items() {
-    rangeClosed(1, 101).forEach(i -> db.rules().insert(r -> r.setLanguage("lang" + i)));
-    index();
-
-    SearchIdResult<String> result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_LANGUAGES)));
-
-    assertThat(result.getFacets().get(FACET_LANGUAGES)).hasSize(100);
-  }
-
-  @Test
-  public void repositories_facet_should_return_top_100_items() {
-    rangeClosed(1, 101).forEach(i -> db.rules().insert(r -> r.setRepositoryKey("repo" + i)));
-    index();
-
-    SearchIdResult<String> result = underTest.search(new RuleQuery(), new SearchOptions().addFacets(singletonList(FACET_REPOSITORIES)));
-
-    assertThat(result.getFacets().get(FACET_REPOSITORIES)).hasSize(100);
-  }
-
-  @Test
-  public void tags_facet_should_find_tags() {
-    createRule(setSystemTags(), setTags("bla"));
-    index();
-
-    RuleQuery query = new RuleQuery();
-    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
-
-    SearchIdResult<String> result = underTest.search(query, options);
-    assertThat(result.getFacets().get(FACET_TAGS)).contains(entry("bla", 1L));
-  }
-
-  @Test
-  public void tags_facet_should_return_top_100_items() {
-    // default number of items returned in tag facet = 100
-    String[] tags = get101Tags();
-    createRule(setSystemTags(tags));
-    index();
-
-    RuleQuery query = new RuleQuery();
-    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
-    SearchIdResult<String> result = underTest.search(query, options);
-    assertThat(result.getFacets().get(FACET_TAGS)).hasSize(100);
-    assertThat(result.getFacets().get(FACET_TAGS)).contains(entry("tag0", 1L), entry("tag25", 1L), entry("tag99", 1L));
-    assertThat(result.getFacets().get(FACET_TAGS)).doesNotContain(entry("tagA", 1L));
-  }
-
-  @Test
-  public void tags_facet_should_include_matching_selected_items() {
-    // default number of items returned in tag facet = 100
-    String[] tags = get101Tags();
-    createRule(setSystemTags(tags));
-    index();
-
-    RuleQuery query = new RuleQuery()
-      .setTags(singletonList("tagA"));
-    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
-    SearchIdResult<String> result = underTest.search(query, options);
-    assertThat(result.getFacets().get(FACET_TAGS)).hasSize(101);
-    assertThat(result.getFacets().get(FACET_TAGS).entrySet()).extracting(e -> entry(e.getKey(), e.getValue())).contains(
-
-      // check that selected item is added, although there are 100 other items
-      entry("tagA", 1L),
-
-      entry("tag0", 1L), entry("tag25", 1L), entry("tag99", 1L));
-  }
-
-  @Test
-  public void tags_facet_should_be_available() {
-    RuleQuery query = new RuleQuery();
-    SearchOptions options = new SearchOptions().addFacets(singletonList(FACET_TAGS));
-
-    SearchIdResult<String> result = underTest.search(query, options);
-    assertThat(result.getFacets().get(FACET_TAGS)).isNotNull();
-  }
-
-  /**
-   * Facet with 2 filters
-   * -- lang facet for tag T2
-   * -- tag facet for lang cpp
-   * -- repository for cpp & T2
-   */
-  @Test
-  public void sticky_facets_with_2_filters() {
-    setupStickyFacets();
-
-    RuleQuery query = new RuleQuery()
-      .setLanguages(ImmutableList.of("cpp"))
-      .setTags(ImmutableList.of("T2"));
-
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES,
-      FACET_TAGS)));
-    assertThat(result.getUuids()).hasSize(1);
-    Facets facets = result.getFacets();
-    assertThat(facets.getAll()).hasSize(3);
-    assertThat(facets.get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java");
-    assertThat(facets.get(FACET_REPOSITORIES)).containsOnlyKeys("foo");
-    assertThat(facets.get(FACET_TAGS)).containsOnlyKeys("T2", "T3");
-  }
-
-  /**
-   * Facet with 3 filters
-   * -- lang facet for tag T2
-   * -- tag facet for lang cpp & java
-   * -- repository for (cpp || java) & T2
-   * -- type
-   */
-  @Test
-  public void sticky_facets_with_3_filters() {
-    setupStickyFacets();
-
-    RuleQuery query = new RuleQuery()
-      .setLanguages(ImmutableList.of("cpp", "java"))
-      .setTags(ImmutableList.of("T2"))
-      .setTypes(asList(BUG, CODE_SMELL));
-
-    SearchIdResult<String> result = underTest.search(query, new SearchOptions().addFacets(asList(FACET_LANGUAGES, FACET_REPOSITORIES, FACET_TAGS,
-      FACET_TYPES)));
-    assertThat(result.getUuids()).hasSize(2);
-    assertThat(result.getFacets().getAll()).hasSize(4);
-    assertThat(result.getFacets().get(FACET_LANGUAGES)).containsOnlyKeys("cpp", "java");
-    assertThat(result.getFacets().get(FACET_REPOSITORIES)).containsOnlyKeys("foo", "xoo");
-    assertThat(result.getFacets().get(FACET_TAGS)).containsOnlyKeys("T1", "T2", "T3");
-    assertThat(result.getFacets().get(FACET_TYPES)).containsOnlyKeys("CODE_SMELL");
-  }
-
-  @Test
-  public void sort_by_name() {
-    RuleDto abcd = createRule(setName("abcd"));
-    RuleDto abc = createRule(setName("ABC"));
-    RuleDto fgh = createRule(setName("FGH"));
-    index();
-
-    // ascending
-    RuleQuery query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME);
-    SearchIdResult<String> results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsExactly(abc.getUuid(), abcd.getUuid(), fgh.getUuid());
-
-    // descending
-    query = new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_NAME).setAscendingSort(false);
-    results = underTest.search(query, new SearchOptions());
-    assertThat(results.getUuids()).containsExactly(fgh.getUuid(), abcd.getUuid(), abc.getUuid());
-  }
-
-  @Test
-  public void default_sort_is_by_updated_at_desc() {
-    long currentTimeMillis = System.currentTimeMillis();
-
-    RuleDto old = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 1000L));
-    RuleDto oldest = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 3000L));
-    RuleDto older = createRule(setCreatedAt(1000L), setUpdatedAt(currentTimeMillis + 2000L));
-    index();
-
-    SearchIdResult<String> results = underTest.search(new RuleQuery(), new SearchOptions());
-    assertThat(results.getUuids()).containsExactly(oldest.getUuid(), older.getUuid(), old.getUuid());
-  }
-
-  @Test
-  public void fail_sort_by_language() {
-    try {
-      // Sorting on a field not tagged as sortable
-      new RuleQuery().setSortField(RuleIndexDefinition.FIELD_RULE_LANGUAGE);
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e).hasMessage("Field 'lang' is not sortable");
-    }
-  }
-
-  @Test
-  public void paging() {
-    createRule();
-    createRule();
-    createRule();
-    index();
-
-    // from 0 to 1 included
-    SearchOptions options = new SearchOptions();
-    options.setOffset(0).setLimit(2);
-    SearchIdResult<String> results = underTest.search(new RuleQuery(), options);
-    assertThat(results.getTotal()).isEqualTo(3);
-    assertThat(results.getUuids()).hasSize(2);
-
-    // from 0 to 9 included
-    options.setOffset(0).setLimit(10);
-    results = underTest.search(new RuleQuery(), options);
-    assertThat(results.getTotal()).isEqualTo(3);
-    assertThat(results.getUuids()).hasSize(3);
-
-    // from 2 to 11 included
-    options.setOffset(2).setLimit(10);
-    results = underTest.search(new RuleQuery(), options);
-    assertThat(results.getTotal()).isEqualTo(3);
-    assertThat(results.getUuids()).hasSize(1);
-  }
-
-  @Test
-  public void search_all_keys_by_query() {
-    createRule(setRepositoryKey("javascript"), setRuleKey("X001"));
-    createRule(setRepositoryKey("cobol"), setRuleKey("X001"));
-    createRule(setRepositoryKey("php"), setRuleKey("S002"));
-    index();
-
-    // key
-    assertThat(underTest.searchAll(new RuleQuery().setQueryText("X001"))).toIterable().hasSize(2);
-
-    // partial key does not match
-    assertThat(underTest.searchAll(new RuleQuery().setQueryText("X00"))).toIterable().isEmpty();
-
-    // repo:key -> nice-to-have !
-    assertThat(underTest.searchAll(new RuleQuery().setQueryText("javascript:X001"))).toIterable().hasSize(1);
-  }
-
-  @Test
-  public void searchAll_keys_by_profile() {
-    RuleDto rule1 = createRule();
-    RuleDto rule2 = createRule();
-    RuleDto rule3 = createRule();
-    QProfileDto profile1 = createJavaProfile();
-    QProfileDto profile2 = createJavaProfile();
-    db.qualityProfiles().activateRule(profile1, rule1);
-    db.qualityProfiles().activateRule(profile2, rule1);
-    db.qualityProfiles().activateRule(profile1, rule2);
-    index();
-
-    // inactive rules on profile
-    es.getDocuments(TYPE_RULE);
-    es.getDocuments(TYPE_ACTIVE_RULE);
-    assertThat(underTest.searchAll(new RuleQuery().setActivation(false).setQProfile(profile2)))
-      .toIterable()
-      .containsOnly(rule2.getUuid(), rule3.getUuid());
-
-    // active rules on profile
-    assertThat(underTest.searchAll(new RuleQuery().setActivation(true).setQProfile(profile2)))
-      .toIterable()
-      .containsOnly(rule1.getUuid());
-  }
-
-  private String[] get101Tags() {
-    String[] tags = new String[101];
-    for (int i = 0; i < 100; i++) {
-      tags[i] = "tag" + i;
-    }
-    tags[100] = "tagA";
-    return tags;
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java
deleted file mode 100644 (file)
index 9ab742a..0000000
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.rule.index;
-
-import com.google.common.collect.ImmutableSet;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDescriptionSectionDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Scope;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.security.SecurityStandards;
-import org.sonar.server.security.SecurityStandards.SQCategory;
-
-import static com.google.common.collect.Sets.newHashSet;
-import static java.lang.String.format;
-import static java.util.Collections.emptyList;
-import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toSet;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
-import static org.sonar.db.rule.RuleTesting.newRule;
-import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
-import static org.sonar.server.security.SecurityStandards.CWES_BY_SQ_CATEGORY;
-import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING;
-
-@RunWith(DataProviderRunner.class)
-public class RuleIndexerTest {
-
-  private static final String VALID_HOTSPOT_RULE_DESCRIPTION = "acme\n" +
-    "<h2>Ask Yourself Whether</h2>\n" +
-    "bar\n" +
-    "<h2>Recommended Secure Coding Practices</h2>\n" +
-    "foo";
-
-  private static final UuidFactoryFast uuidFactory = UuidFactoryFast.getInstance();
-  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_DTO = createDefaultRuleDescriptionSection(uuidFactory.create(), VALID_HOTSPOT_RULE_DESCRIPTION);
-  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_DTO2 = RuleDescriptionSectionDto.builder()
-    .uuid(uuidFactory.create())
-    .key("section2")
-    .content("rule descriptions section 2")
-    .build();
-
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public DbTester dbTester = DbTester.create();
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final RuleIndexer underTest = new RuleIndexer(es.client(), dbClient);
-  private final DbSession dbSession = dbTester.getSession();
-  private final RuleDto rule = new RuleDto()
-    .setUuid("rule-uuid")
-    .setRuleKey("S001")
-    .setRepositoryKey("xoo")
-    .setConfigKey("S1")
-    .setName("Null Pointer")
-    .setDescriptionFormat(RuleDto.Format.HTML)
-    .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_DTO)
-    .setLanguage("xoo")
-    .setSeverity(Severity.BLOCKER)
-    .setStatus(RuleStatus.READY)
-    .setIsTemplate(true)
-    .setSystemTags(newHashSet("cwe"))
-    .setType(RuleType.BUG)
-    .setScope(Scope.ALL)
-    .setCreatedAt(1500000000000L)
-    .setUpdatedAt(1600000000000L);
-
-  @Test
-  public void index_nothing() {
-    underTest.index(dbSession, emptyList());
-    assertThat(es.countDocuments(TYPE_RULE)).isZero();
-  }
-
-  @Test
-  public void index() {
-    dbClient.ruleDao().insert(dbSession, rule);
-    underTest.commitAndIndex(dbSession, rule.getUuid());
-
-    assertThat(es.countDocuments(TYPE_RULE)).isOne();
-  }
-
-  @Test
-  public void removed_rule_is_not_removed_from_index() {
-    // Create and Index rule
-    dbClient.ruleDao().insert(dbSession, rule.setStatus(RuleStatus.READY));
-    dbSession.commit();
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-    assertThat(es.countDocuments(TYPE_RULE)).isOne();
-
-    // Remove rule
-    dbTester.getDbClient().ruleDao().update(dbTester.getSession(), rule.setStatus(RuleStatus.READY).setUpdatedAt(2000000000000L));
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-
-    assertThat(es.countDocuments(TYPE_RULE)).isOne();
-  }
-
-  @Test
-  public void index_long_rule_description() {
-    RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(uuidFactory.create(), randomAlphanumeric(100000));
-    RuleDto rule = dbTester.rules().insert(newRule(ruleDescriptionSectionDto));
-
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-
-    assertThat(es.countDocuments(TYPE_RULE)).isOne();
-  }
-
-  @Test
-  public void index_long_rule_with_several_sections() {
-    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO, RULE_DESCRIPTION_SECTION_DTO2));
-
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-
-    List<RuleDoc> ruleDocs = es.getDocuments(TYPE_RULE, RuleDoc.class);
-    assertThat(ruleDocs).hasSize(1);
-    assertThat(ruleDocs.iterator().next().htmlDescription())
-      .contains(RULE_DESCRIPTION_SECTION_DTO.getContent())
-      .contains(RULE_DESCRIPTION_SECTION_DTO2.getContent())
-      .hasSize(RULE_DESCRIPTION_SECTION_DTO.getContent().length() + " ".length() + RULE_DESCRIPTION_SECTION_DTO2.getContent().length());
-  }
-
-  @Test
-  public void index_long_rule_with_section_in_markdown() {
-    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO).setDescriptionFormat(RuleDto.Format.MARKDOWN));
-
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-
-    List<RuleDoc> ruleDocs = es.getDocuments(TYPE_RULE, RuleDoc.class);
-    assertThat(ruleDocs).hasSize(1);
-    assertThat(ruleDocs.iterator().next().htmlDescription())
-      .isEqualTo("acme<br/>&lt;h2&gt;Ask Yourself Whether&lt;/h2&gt;<br/>bar<br/>"
-        + "&lt;h2&gt;Recommended Secure Coding Practices&lt;/h2&gt;<br/>foo");
-  }
-
-  @Test
-  @UseDataProvider("twoDifferentCategoriesButOTHERS")
-  public void log_debug_if_hotspot_rule_maps_to_multiple_SQCategories(SQCategory sqCategory1, SQCategory sqCategory2) {
-    Set<String> standards = Stream.of(sqCategory1, sqCategory2)
-      .flatMap(t -> CWES_BY_SQ_CATEGORY.get(t).stream().map(e -> "cwe:" + e))
-      .collect(toSet());
-    SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(standards);
-    RuleDto rule = dbTester.rules().insert(newRule(RULE_DESCRIPTION_SECTION_DTO)
-      .setType(RuleType.SECURITY_HOTSPOT)
-      .setSecurityStandards(standards));
-    underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
-
-    assertThat(logTester.getLogs()).hasSize(1);
-    assertThat(logTester.logs(LoggerLevel.DEBUG).get(0))
-      .isEqualTo(format(
-        "Rule %s with CWEs '%s' maps to multiple SQ Security Categories: %s",
-        rule.getKey(),
-        String.join(", ", securityStandards.getCwe()),
-        ImmutableSet.of(sqCategory1, sqCategory2).stream()
-          .map(SQCategory::getKey)
-          .sorted(SQ_CATEGORY_KEYS_ORDERING)
-          .collect(joining(", "))));
-  }
-
-  @DataProvider
-  public static Object[][] twoDifferentCategoriesButOTHERS() {
-    EnumSet<SQCategory> sqCategories = EnumSet.allOf(SQCategory.class);
-    sqCategories.remove(SQCategory.OTHERS);
-
-    // pick two random categories
-    Random random = new Random();
-    SQCategory sqCategory1 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())];
-    sqCategories.remove(sqCategory1);
-    SQCategory sqCategory2 = sqCategories.toArray(new SQCategory[0])[random.nextInt(sqCategories.size())];
-    return new Object[][]{
-      {sqCategory1, sqCategory2}
-    };
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/setting/DatabaseSettingLoaderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/setting/DatabaseSettingLoaderTest.java
deleted file mode 100644 (file)
index 25f3e70..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.setting;
-
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.property.PropertyDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-
-public class DatabaseSettingLoaderTest {
-
-  private static final String A_KEY = "a_key";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DatabaseSettingLoader underTest = new DatabaseSettingLoader(dbTester.getDbClient());
-
-  @Test
-  public void test_load() {
-    insertPropertyIntoDb(A_KEY, "foo");
-
-    assertThat(underTest.load(A_KEY)).isEqualTo("foo");
-    assertThat(underTest.load("missing")).isNull();
-  }
-
-  @Test
-  public void null_value_in_db_is_considered_as_empty_string() {
-    insertPropertyIntoDb(A_KEY, null);
-
-    assertThat(underTest.load(A_KEY)).isEmpty();
-  }
-
-  @Test
-  public void test_empty_value_in_db() {
-    insertPropertyIntoDb(A_KEY, "");
-    assertThat(underTest.load(A_KEY)).isEmpty();
-  }
-
-  @Test
-  public void test_loadAll_with_no_properties() {
-    Map<String, String> map = underTest.loadAll();
-    assertThat(map).isEmpty();
-  }
-
-  @Test
-  public void test_loadAll() {
-    insertPropertyIntoDb("foo", "1");
-    insertPropertyIntoDb("bar", "2");
-
-    Map<String, String> map = underTest.loadAll();
-
-    assertThat(map).containsOnly(entry("foo", "1"), entry("bar", "2"));
-  }
-
-  private void insertPropertyIntoDb(String key, String value) {
-    dbTester.getDbClient().propertiesDao().saveProperty(new PropertyDto().setKey(key).setValue(value));
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/setting/ProjectConfigurationLoaderImplTest.java
deleted file mode 100644 (file)
index b27b303..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.setting;
-
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.property.PropertiesDao;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ProjectConfigurationLoaderImplTest {
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private final String globalPropKey = randomAlphanumeric(9);
-  private final String globalPropValue = randomAlphanumeric(10);
-  private final String mainBranchPropKey = randomAlphanumeric(7);
-  private final String mainBranchPropValue = randomAlphanumeric(8);
-  private final String branchPropKey = randomAlphanumeric(9);
-  private final String branchPropValue = randomAlphanumeric(10);
-
-  private final String mainBranchUuid = randomAlphanumeric(6);
-  private final String branchUuid = randomAlphanumeric(6);
-  private final MapSettings globalSettings = new MapSettings();
-  private ProjectConfigurationLoaderImpl underTest;
-
-  @Before
-  public void setUp() {
-    underTest = new ProjectConfigurationLoaderImpl(globalSettings, db.getDbClient());
-  }
-
-  @Test
-  public void return_configuration_with_just_global_settings_when_no_component_settings() {
-    globalSettings.setProperty(mainBranchPropKey, mainBranchPropValue);
-    ComponentDto component = newComponentDto(mainBranchUuid);
-
-    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
-
-    assertThat(configuration.get(mainBranchPropKey)).contains(mainBranchPropValue);
-  }
-
-  @Test
-  public void return_configuration_with_global_settings_and_component_settings() {
-    String projectPropKey1 = randomAlphanumeric(7);
-    String projectPropValue1 = randomAlphanumeric(8);
-    String projectPropKey2 = randomAlphanumeric(9);
-    String projectPropValue2 = randomAlphanumeric(10);
-    globalSettings.setProperty(globalPropKey, globalPropValue);
-    db.properties().insertProperty(projectPropKey1, projectPropValue1, mainBranchUuid);
-    db.properties().insertProperty(projectPropKey2, projectPropValue2, mainBranchUuid);
-    ComponentDto component = newComponentDto(mainBranchUuid);
-
-    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
-
-    assertThat(configuration.get(globalPropKey)).contains(globalPropValue);
-    assertThat(configuration.get(projectPropKey1)).contains(projectPropValue1);
-    assertThat(configuration.get(projectPropKey2)).contains(projectPropValue2);
-  }
-
-  @Test
-  public void return_configuration_with_global_settings_main_branch_settings_and_branch_settings() {
-    globalSettings.setProperty(globalPropKey, globalPropValue);
-
-    db.properties().insertProperty(mainBranchPropKey, mainBranchPropValue, mainBranchUuid);
-    db.properties().insertProperty(branchPropKey, branchPropValue, branchUuid);
-
-    ComponentDto component = newComponentDto(branchUuid, mainBranchUuid);
-    Configuration configuration = underTest.loadProjectConfiguration(db.getSession(), component);
-
-    assertThat(configuration.get(globalPropKey)).contains(globalPropValue);
-    assertThat(configuration.get(mainBranchPropKey)).contains(mainBranchPropValue);
-    assertThat(configuration.get(branchPropKey)).contains(branchPropValue);
-  }
-
-  private ComponentDto newComponentDto(String uuid) {
-    return newComponentDto(uuid, null);
-  }
-
-  private ComponentDto newComponentDto(String uuid, @Nullable String mainBranchUuid) {
-    return new ComponentDto().setUuid(uuid).setBranchUuid(uuid).setMainBranchProjectUuid(mainBranchUuid);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexTest.java
deleted file mode 100644 (file)
index c75c1ed..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.user.index;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.Uuids;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchOptions;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
-
-public class UserIndexTest {
-
-  private static final String USER1_LOGIN = "user1";
-  private static final String USER2_LOGIN = "user2";
-  private static final String USER3_LOGIN = "user3";
-
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private UserIndex underTest = new UserIndex(es.client(), System2.INSTANCE);
-  private UserQuery.Builder userQuery = UserQuery.builder();
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_returns_the_users_with_specified_scm_account() {
-    UserDoc user1 = newUser("user1", asList("user_1", "u1"));
-    UserDoc user2 = newUser("user_with_same_email_as_user1", asList("user_2")).setEmail(user1.email());
-    UserDoc user3 = newUser("inactive_user_with_same_scm_as_user1", user1.scmAccounts()).setActive(false);
-    es.putDocuments(TYPE_USER, user1);
-    es.putDocuments(TYPE_USER, user2);
-    es.putDocuments(TYPE_USER, user3);
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.scmAccounts().get(0))).extractingResultOf("login").containsOnly(user1.login());
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.login())).extractingResultOf("login").containsOnly(user1.login());
-
-    // both users share the same email
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user1.email())).extracting(UserDoc::login).containsOnly(user1.login(), user2.login());
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("")).isEmpty();
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("unknown")).isEmpty();
-  }
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_ignores_inactive_user() {
-    String scmAccount = "scmA";
-    UserDoc user = newUser(USER1_LOGIN, singletonList(scmAccount)).setActive(false);
-    es.putDocuments(TYPE_USER, user);
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(user.login())).isEmpty();
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(scmAccount)).isEmpty();
-  }
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_returns_maximum_three_users() {
-    String email = "user@mail.com";
-    UserDoc user1 = newUser("user1", Collections.emptyList()).setEmail(email);
-    UserDoc user2 = newUser("user2", Collections.emptyList()).setEmail(email);
-    UserDoc user3 = newUser("user3", Collections.emptyList()).setEmail(email);
-    UserDoc user4 = newUser("user4", Collections.emptyList()).setEmail(email);
-    es.putDocuments(TYPE_USER, user1);
-    es.putDocuments(TYPE_USER, user2);
-    es.putDocuments(TYPE_USER, user3);
-    es.putDocuments(TYPE_USER, user4);
-
-    // restrict results to 3 users
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount(email)).hasSize(3);
-  }
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_is_case_sensitive_for_login() {
-    UserDoc user = newUser("the_login", singletonList("John.Smith"));
-    es.putDocuments(TYPE_USER, user);
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_login")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_Login")).isEmpty();
-  }
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_email() {
-    UserDoc user = newUser("the_login", "the_EMAIL@corp.com", singletonList("John.Smith"));
-    es.putDocuments(TYPE_USER, user);
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_EMAIL@corp.com")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("the_email@corp.com")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("email")).isEmpty();
-  }
-
-  @Test
-  public void getAtMostThreeActiveUsersForScmAccount_is_case_insensitive_for_scm_account() {
-    UserDoc user = newUser("the_login", singletonList("John.Smith"));
-    es.putDocuments(TYPE_USER, user);
-
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("John.Smith")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN.SMIth")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN.SMITH")).hasSize(1);
-    assertThat(underTest.getAtMostThreeActiveUsersForScmAccount("JOHN")).isEmpty();
-  }
-
-  @Test
-  public void searchUsers() {
-    es.putDocuments(TYPE_USER, newUser(USER1_LOGIN, asList("user_1", "u1")).setEmail("email1"));
-    es.putDocuments(TYPE_USER, newUser(USER2_LOGIN, Collections.emptyList()).setEmail("email2"));
-    es.putDocuments(TYPE_USER, newUser(USER3_LOGIN, Collections.emptyList()).setEmail("email3").setActive(false));
-
-    assertThat(underTest.search(userQuery.build(), new SearchOptions()).getDocs()).hasSize(2);
-    assertThat(underTest.search(userQuery.setTextQuery("user").build(), new SearchOptions()).getDocs()).hasSize(2);
-    assertThat(underTest.search(userQuery.setTextQuery("ser").build(), new SearchOptions()).getDocs()).hasSize(2);
-    assertThat(underTest.search(userQuery.setTextQuery(USER1_LOGIN).build(), new SearchOptions()).getDocs()).hasSize(1);
-    assertThat(underTest.search(userQuery.setTextQuery(USER2_LOGIN).build(), new SearchOptions()).getDocs()).hasSize(1);
-    assertThat(underTest.search(userQuery.setTextQuery("mail").build(), new SearchOptions()).getDocs()).hasSize(2);
-    assertThat(underTest.search(userQuery.setTextQuery("EMAIL1").build(), new SearchOptions()).getDocs()).hasSize(1);
-
-    // deactivated users
-    assertThat(underTest.search(userQuery.setActive(false).setTextQuery(null).build(), new SearchOptions()).getDocs()).hasSize(1);
-    assertThat(underTest.search(userQuery.setActive(false).setTextQuery("email3").build(), new SearchOptions()).getDocs()).hasSize(1);
-  }
-
-  private static UserDoc newUser(String login, List<String> scmAccounts) {
-    return new UserDoc()
-      .setUuid(Uuids.createFast())
-      .setLogin(login)
-      .setName(login.toUpperCase(Locale.ENGLISH))
-      .setEmail(login + "@mail.com")
-      .setActive(true)
-      .setScmAccounts(scmAccounts);
-  }
-
-  private static UserDoc newUser(String login, String email, List<String> scmAccounts) {
-    return new UserDoc()
-      .setUuid(Uuids.createFast())
-      .setLogin(login)
-      .setName(login.toUpperCase(Locale.ENGLISH))
-      .setEmail(email)
-      .setActive(true)
-      .setScmAccounts(scmAccounts);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/user/index/UserIndexerTest.java
deleted file mode 100644 (file)
index 707361e..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.user.index;
-
-import java.util.HashSet;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.user.index.UserIndexDefinition.TYPE_USER;
-
-public class UserIndexerTest {
-
-  private final System2 system2 = System2.INSTANCE;
-
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private final UserIndexer underTest = new UserIndexer(db.getDbClient(), es.client());
-
-  @Test
-  public void index_nothing_on_startup() {
-    underTest.indexOnStartup(new HashSet<>());
-
-    assertThat(es.countDocuments(TYPE_USER)).isZero();
-  }
-
-  @Test
-  public void indexOnStartup_adds_all_users_to_index() {
-    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("user_1", "u1")));
-
-    underTest.indexOnStartup(new HashSet<>());
-
-    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
-    assertThat(docs).hasSize(1);
-    UserDoc doc = docs.get(0);
-    assertThat(doc.uuid()).isEqualTo(user.getUuid());
-    assertThat(doc.login()).isEqualTo(user.getLogin());
-    assertThat(doc.name()).isEqualTo(user.getName());
-    assertThat(doc.email()).isEqualTo(user.getEmail());
-    assertThat(doc.active()).isEqualTo(user.isActive());
-    assertThat(doc.scmAccounts()).isEqualTo(user.getScmAccountsAsList());
-  }
-
-  @Test
-  public void indexAll_adds_all_users_to_index() {
-    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("user_1", "u1")));
-
-    underTest.indexAll();
-
-    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
-    assertThat(docs).hasSize(1);
-    UserDoc doc = docs.get(0);
-    assertThat(doc.uuid()).isEqualTo(user.getUuid());
-    assertThat(doc.login()).isEqualTo(user.getLogin());
-    assertThat(doc.name()).isEqualTo(user.getName());
-    assertThat(doc.email()).isEqualTo(user.getEmail());
-    assertThat(doc.active()).isEqualTo(user.isActive());
-    assertThat(doc.scmAccounts()).isEqualTo(user.getScmAccountsAsList());
-  }
-
-  @Test
-  public void indexOnStartup_adds_all_users() {
-    UserDto user = db.users().insertUser();
-
-    underTest.indexOnStartup(new HashSet<>());
-
-    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
-    assertThat(docs).hasSize(1);
-    UserDoc doc = docs.get(0);
-    assertThat(doc.uuid()).isEqualTo(user.getUuid());
-    assertThat(doc.login()).isEqualTo(user.getLogin());
-  }
-
-  @Test
-  public void commitAndIndex_single_user() {
-    UserDto user = db.users().insertUser();
-    UserDto anotherUser = db.users().insertUser();
-
-    underTest.commitAndIndex(db.getSession(), user);
-
-    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
-    assertThat(docs)
-      .extracting(UserDoc::uuid)
-      .containsExactlyInAnyOrder(user.getUuid())
-      .doesNotContain(anotherUser.getUuid());
-  }
-
-  @Test
-  public void commitAndIndex_multiple_users() {
-    UserDto user1 = db.users().insertUser();
-    UserDto user2 = db.users().insertUser();
-
-    underTest.commitAndIndex(db.getSession(), asList(user1, user2));
-
-    List<UserDoc> docs = es.getDocuments(TYPE_USER, UserDoc.class);
-    assertThat(docs)
-      .extracting(UserDoc::login)
-      .containsExactlyInAnyOrder(
-        user1.getLogin(),
-        user2.getLogin());
-    assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(2);
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexTest.java
deleted file mode 100644 (file)
index 301f585..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.view.index;
-
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.server.es.EsTester;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
-
-public class ViewIndexTest {
-
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private ViewIndex index = new ViewIndex(es.client());
-
-  @Test
-  public void find_all_view_uuids() {
-    ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(singletonList("P1"));
-    ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(singletonList("P2"));
-    es.putDocuments(TYPE_VIEW, view1);
-    es.putDocuments(TYPE_VIEW, view2);
-
-    List<String> result = newArrayList(index.findAllViewUuids());
-
-    assertThat(result).containsOnly(view1.uuid(), view2.uuid());
-  }
-
-  @Test
-  public void not_find_all_view_uuids() {
-    List<String> result = newArrayList(index.findAllViewUuids());
-
-    assertThat(result).isEmpty();
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/view/index/ViewIndexerTest.java
deleted file mode 100644 (file)
index 51cadcc..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.view.index;
-
-import com.google.common.collect.Maps;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.es.EsTester;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptySet;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.sonar.api.resources.Qualifiers.APP;
-import static org.sonar.db.component.ComponentTesting.newProjectCopy;
-import static org.sonar.db.component.ComponentTesting.newSubPortfolio;
-import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
-
-public class ViewIndexerTest {
-
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-  @Rule
-  public DbTester db = DbTester.create();
-  @Rule
-  public EsTester es = EsTester.create();
-
-  private final DbClient dbClient = db.getDbClient();
-  private final DbSession dbSession = db.getSession();
-  private final ViewIndexer underTest = new ViewIndexer(dbClient, es.client());
-
-  @Test
-  public void getIndexTypes() {
-    assertThat(underTest.getIndexTypes()).containsExactly(TYPE_VIEW);
-  }
-
-  @Test
-  public void index_nothing() {
-    underTest.indexOnStartup(emptySet());
-    assertThat(es.countDocuments(TYPE_VIEW)).isZero();
-  }
-
-  @Test
-  public void index_on_startup() {
-    // simple view
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto view1 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view1, t -> t.setLast(true));
-    db.components().insertComponent(newProjectCopy(project1, view1));
-    // view with subview
-    ComponentDto project2 = db.components().insertPrivateProject();
-    ComponentDto project3 = db.components().insertPrivateProject();
-    ComponentDto view2 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view2, t -> t.setLast(true));
-    db.components().insertComponent(newProjectCopy(project2, view2));
-    ComponentDto subView = db.components().insertComponent(newSubPortfolio(view2));
-    db.components().insertComponent(newProjectCopy(project3, subView));
-    // view without project
-    ComponentDto view3 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view3, t -> t.setLast(true));
-
-    underTest.indexOnStartup(emptySet());
-
-    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-    assertThat(docs).hasSize(4);
-
-    Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
-
-    assertThat(viewsByUuid.get(view1.uuid()).projects()).containsOnly(project1.uuid());
-    assertThat(viewsByUuid.get(view2.uuid()).projects()).containsOnly(project2.uuid(), project3.uuid());
-    assertThat(viewsByUuid.get(subView.uuid()).projects()).containsOnly(project3.uuid());
-    assertThat(viewsByUuid.get(view3.uuid()).projects()).isEmpty();
-  }
-
-  @Test
-  public void index_root_view() {
-    // simple view
-    ComponentDto project1 = db.components().insertPrivateProject();
-    ComponentDto view1 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view1, t -> t.setLast(true));
-    db.components().insertComponent(newProjectCopy(project1, view1));
-    // view with subview
-    ComponentDto project2 = db.components().insertPrivateProject();
-    ComponentDto project3 = db.components().insertPrivateProject();
-    ComponentDto view2 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view2, t -> t.setLast(true));
-    db.components().insertComponent(newProjectCopy(project2, view2));
-    ComponentDto subView = db.components().insertComponent(newSubPortfolio(view2));
-    db.components().insertComponent(newProjectCopy(project3, subView));
-    // view without project
-    ComponentDto view3 = db.components().insertPrivatePortfolio();
-    db.components().insertSnapshot(view3, t -> t.setLast(true));
-
-    underTest.index(view2.uuid());
-
-    List<ViewDoc> docs = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-    assertThat(docs).hasSize(2);
-
-    Map<String, ViewDoc> viewsByUuid = Maps.uniqueIndex(docs, ViewDoc::uuid);
-
-    assertThat(viewsByUuid.get(view2.uuid()).projects()).containsOnly(project2.uuid(), project3.uuid());
-    assertThat(viewsByUuid.get(subView.uuid()).projects()).containsOnly(project3.uuid());
-  }
-
-  @Test
-  public void index_view_doc() {
-    underTest.index(new ViewDoc().setUuid("EFGH").setProjects(newArrayList("KLMN", "JKLM")));
-
-    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-
-    assertThat(result).hasSize(1);
-    ViewDoc view = result.get(0);
-    assertThat(view.uuid()).isEqualTo("EFGH");
-    assertThat(view.projects()).containsOnly("KLMN", "JKLM");
-  }
-
-  @Test
-  public void index_application() {
-    ComponentDto application = db.components().insertPrivateApplication();
-    ComponentDto project = db.components().insertPrivateProject();
-    db.components().insertComponent(newProjectCopy("PC1", project, application));
-
-    underTest.index(application.uuid());
-    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-
-    assertThat(result).hasSize(1);
-    ViewDoc resultApp = result.get(0);
-    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
-    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void index_application_on_startup() {
-    ComponentDto application = db.components().insertPrivateApplication();
-    ComponentDto project = db.components().insertPrivateProject();
-    db.components().insertComponent(newProjectCopy("PC1", project, application));
-
-    underTest.indexOnStartup(emptySet());
-    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-
-    assertThat(result).hasSize(1);
-    ViewDoc resultApp = result.get(0);
-    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
-    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void index_application_with_indexAll() {
-    ComponentDto application = db.components().insertPrivateApplication();
-    ComponentDto project = db.components().insertPrivateProject();
-    db.components().insertComponent(newProjectCopy("PC1", project, application));
-
-    underTest.indexAll();
-    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-
-    assertThat(result).hasSize(1);
-    ViewDoc resultApp = result.get(0);
-    assertThat(resultApp.uuid()).isEqualTo(application.uuid());
-    assertThat(resultApp.projects()).containsExactlyInAnyOrder(project.uuid());
-  }
-
-  @Test
-  public void index_application_branch() {
-    ComponentDto application = db.components().insertPublicProject(c -> c.setQualifier(APP).setKey("app"));
-    ComponentDto applicationBranch1 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch1"));
-    ComponentDto applicationBranch2 = db.components().insertProjectBranch(application, a -> a.setKey("app-branch2"));
-    ComponentDto project1 = db.components().insertPrivateProject(p -> p.setKey("prj1"));
-    ComponentDto project1Branch = db.components().insertProjectBranch(project1);
-    ComponentDto project2 = db.components().insertPrivateProject(p -> p.setKey("prj2"));
-    ComponentDto project2Branch = db.components().insertProjectBranch(project2);
-    ComponentDto project3 = db.components().insertPrivateProject(p -> p.setKey("prj3"));
-    ComponentDto project3Branch = db.components().insertProjectBranch(project3);
-    db.components().insertComponent(newProjectCopy(project1Branch, applicationBranch1));
-    db.components().insertComponent(newProjectCopy(project2Branch, applicationBranch1));
-    // Technical project of applicationBranch2 should be ignored
-    db.components().insertComponent(newProjectCopy(project3Branch, applicationBranch2));
-
-    underTest.index(applicationBranch1.uuid());
-
-    List<ViewDoc> result = es.getDocuments(TYPE_VIEW, ViewDoc.class);
-    assertThat(result)
-      .extracting(ViewDoc::uuid, ViewDoc::projects)
-      .containsExactlyInAnyOrder(
-        tuple(applicationBranch1.uuid(), asList(project1Branch.uuid(), project2Branch.uuid())));
-  }
-
-  @Test
-  public void delete_should_delete_the_view() {
-    ViewDoc view1 = new ViewDoc().setUuid("UUID1").setProjects(Collections.singletonList("P1"));
-    ViewDoc view2 = new ViewDoc().setUuid("UUID2").setProjects(asList("P2", "P3", "P4"));
-    ViewDoc view3 = new ViewDoc().setUuid("UUID3").setProjects(asList("P2", "P3", "P4"));
-    es.putDocuments(TYPE_VIEW, view1);
-    es.putDocuments(TYPE_VIEW, view2);
-    es.putDocuments(TYPE_VIEW, view3);
-
-    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
-      .containsOnly(view1.uuid(), view2.uuid(), view3.uuid());
-
-    underTest.delete(dbSession, asList(view1.uuid(), view2.uuid()));
-
-    assertThat(es.getDocumentFieldValues(TYPE_VIEW, ViewIndexDefinition.FIELD_UUID))
-      .containsOnly(view3.uuid());
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
deleted file mode 100644 (file)
index acb4ef4..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.webhook;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.ce.posttask.PostProjectAnalysisTask.LogStatistics;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.webhook.WebhookDbTester;
-import org.sonar.server.async.AsyncExecution;
-
-import static java.util.Objects.requireNonNull;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.sonar.db.DbTester.create;
-import static org.sonar.db.webhook.WebhookTesting.newGlobalWebhook;
-
-public class AsynchronousWebHooksImplTest {
-
-  private final System2 system2 = mock(System2.class);
-
-  @Rule
-  public DbTester db = create(system2);
-  private final WebhookDbTester webhookDbTester = db.webhooks();
-  private final ComponentDbTester componentDbTester = db.components();
-
-  private static final long NOW = 1_500_000_000_000L;
-
-  private final TestWebhookCaller caller = new TestWebhookCaller();
-  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
-  private final WebhookPayload mock = mock(WebhookPayload.class);
-  private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
-
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient());
-
-  @Test
-  public void send_global_webhooks() {
-    ComponentDto project = componentDbTester.insertPrivateProject();
-    webhookDbTester.insert(newGlobalWebhook().setName("First").setUrl("http://url1"), null, null);
-    webhookDbTester.insert(newGlobalWebhook().setName("Second").setUrl("http://url2"), null, null);
-
-    caller.enqueueSuccess(NOW, 200, 1_234);
-    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock, mock(LogStatistics.class));
-
-    assertThat(caller.countSent()).isZero();
-    verifyNoInteractions(deliveryStorage);
-
-    asyncExecution.executeRecorded();
-
-    assertThat(caller.countSent()).isEqualTo(2);
-    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(project.uuid());
-  }
-
-  private static class RecordingAsyncExecution implements AsyncExecution {
-    private final List<Runnable> runnableList = new ArrayList<>();
-
-    @Override
-    public void addToQueue(Runnable r) {
-      runnableList.add(requireNonNull(r));
-    }
-
-    public void executeRecorded() {
-      ArrayList<Runnable> runnables = new ArrayList<>(runnableList);
-      runnableList.clear();
-      runnables.forEach(Runnable::run);
-    }
-  }
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
deleted file mode 100644 (file)
index 8e67308..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.webhook;
-
-import java.io.IOException;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.webhook.WebhookDbTester;
-import org.sonar.server.async.AsyncExecution;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.sonar.api.utils.log.LoggerLevel.DEBUG;
-import static org.sonar.db.DbTester.create;
-import static org.sonar.db.webhook.WebhookTesting.newGlobalWebhook;
-import static org.sonar.db.webhook.WebhookTesting.newWebhook;
-
-public class SynchronousWebHooksImplTest {
-
-  private static final long NOW = 1_500_000_000_000L;
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public DbTester db = create();
-  private final DbClient dbClient = db.getDbClient();
-
-  private final WebhookDbTester webhookDbTester = db.webhooks();
-  private final ComponentDbTester componentDbTester = db.components();
-  private final TestWebhookCaller caller = new TestWebhookCaller();
-  private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
-  private final WebhookPayload mock = mock(WebhookPayload.class);
-  private final AsyncExecution synchronousAsyncExecution = Runnable::run;
-  private final PostProjectAnalysisTask.LogStatistics taskStatistics = mock(PostProjectAnalysisTask.LogStatistics.class);
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient);
-
-  @Test
-  public void isEnabled_returns_false_if_no_webhooks() {
-    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
-    assertThat(underTest.isEnabled(projectDto)).isFalse();
-  }
-
-  @Test
-  public void isEnabled_returns_true_if_one_valid_global_webhook() {
-    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
-    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
-
-    assertThat(underTest.isEnabled(projectDto)).isTrue();
-  }
-
-  @Test
-  public void isEnabled_returns_true_if_one_valid_project_webhook() {
-    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
-    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
-
-    assertThat(underTest.isEnabled(projectDto)).isTrue();
-  }
-
-  @Test
-  public void do_nothing_if_no_webhooks() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
-
-    assertThat(caller.countSent()).isZero();
-    assertThat(logTester.logs(DEBUG)).isEmpty();
-    verifyNoInteractions(deliveryStorage);
-  }
-
-  @Test
-  public void populates_log_statistics_even_if_no_webhooks() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock, taskStatistics);
-
-    assertThat(caller.countSent()).isZero();
-    assertThat(logTester.logs(DEBUG)).isEmpty();
-    verifyNoInteractions(deliveryStorage);
-    verifyLogStatistics(0, 0);
-  }
-
-  @Test
-  public void send_global_webhooks() {
-    ComponentDto componentDto = componentDbTester.insertPrivateProject();
-    webhookDbTester.insert(newGlobalWebhook().setName("First").setUrl("http://url1"), null, null);
-    webhookDbTester.insert(newGlobalWebhook().setName("Second").setUrl("http://url2"), null, null);
-    caller.enqueueSuccess(NOW, 200, 1_234);
-    caller.enqueueFailure(NOW, new IOException("Fail to connect"));
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock, taskStatistics);
-
-    assertThat(caller.countSent()).isEqualTo(2);
-    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
-    assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
-    verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(componentDto.uuid());
-    verifyLogStatistics(2, 0);
-  }
-
-  @Test
-  public void send_project_webhooks() {
-    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
-    webhookDbTester.insert(newWebhook(projectDto).setName("First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
-    caller.enqueueSuccess(NOW, 200, 1_234);
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(projectDto.getUuid(), "1", "#1"), () -> mock, taskStatistics);
-
-    assertThat(caller.countSent()).isOne();
-    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
-    verify(deliveryStorage).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(projectDto.getUuid());
-    verifyLogStatistics(0, 1);
-  }
-
-  @Test
-  public void send_global_and_project_webhooks() {
-    ProjectDto projectDto = componentDbTester.insertPrivateProjectDto();
-    webhookDbTester.insert(newWebhook(projectDto).setName("1First").setUrl("http://url1"), projectDto.getKey(), projectDto.getName());
-    webhookDbTester.insert(newWebhook(projectDto).setName("2Second").setUrl("http://url2"), projectDto.getKey(), projectDto.getName());
-    webhookDbTester.insert(newGlobalWebhook().setName("3Third").setUrl("http://url3"), null, null);
-    webhookDbTester.insert(newGlobalWebhook().setName("4Fourth").setUrl("http://url4"), null,null);
-    webhookDbTester.insert(newGlobalWebhook().setName("5Fifth").setUrl("http://url5"), null,null);
-    caller.enqueueSuccess(NOW, 200, 1_234);
-    caller.enqueueFailure(NOW, new IOException("Fail to connect 1"));
-    caller.enqueueFailure(NOW, new IOException("Fail to connect 2"));
-    caller.enqueueSuccess(NOW, 200, 5_678);
-    caller.enqueueSuccess(NOW, 200, 9_256);
-
-    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(projectDto.getUuid(), "1", "#1"), () -> mock, taskStatistics);
-
-    assertThat(caller.countSent()).isEqualTo(5);
-    List<String> debugLogs = logTester.logs(DEBUG);
-    assertThat(debugLogs)
-      .contains("Sent webhook '1First' | url=http://url1 | time=1234ms | status=200")
-      .contains("Failed to send webhook '2Second' | url=http://url2 | message=Fail to connect 1")
-      .contains("Failed to send webhook '3Third' | url=http://url3 | message=Fail to connect 2")
-      .contains("Sent webhook '4Fourth' | url=http://url4 | time=5678ms | status=200")
-      .contains("Sent webhook '5Fifth' | url=http://url5 | time=9256ms | status=200");
-    verify(deliveryStorage, times(5)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(projectDto.getUuid());
-    verifyLogStatistics(3, 2);
-  }
-
-  private void verifyLogStatistics(int global, int project) {
-    verify(taskStatistics).add("globalWebhooks", global);
-    verify(taskStatistics).add("projectWebhooks", project);
-    verifyNoMoreInteractions(taskStatistics);
-  }
-
-}
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java
deleted file mode 100644 (file)
index 47e0e31..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.webhook;
-
-import java.io.IOException;
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.webhook.WebhookDeliveryDto;
-import org.sonar.db.webhook.WebhookDeliveryTesting;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.webhook.WebhookDeliveryTesting.selectAllDeliveryUuids;
-
-public class WebhookDeliveryStorageTest {
-
-  private static final String DELIVERY_UUID = "abcde1234";
-  private static final long NOW = 1_500_000_000_000L;
-  private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000;
-  private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000;
-
-  private final System2 system = mock(System2.class);
-
-  @Rule
-  public final DbTester dbTester = DbTester.create(system);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
-  private UuidFactory uuidFactory = mock(UuidFactory.class);
-  private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory);
-
-  @Test
-  public void persist_generates_uuid_then_inserts_record() {
-    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
-    WebhookDelivery delivery = newBuilderTemplate().build();
-
-    underTest.persist(delivery);
-
-    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
-    assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID);
-    assertThat(dto.getWebhookUuid()).isEqualTo("WEBHOOK_UUID_1");
-    assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid());
-    assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid().get());
-    assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName());
-    assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl());
-    assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt());
-    assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get());
-    assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get());
-    assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().getJson());
-    assertThat(dto.getErrorStacktrace()).isNull();
-  }
-
-  @Test
-  public void persist_error_stacktrace() {
-    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setError(new IOException("fail to connect"))
-      .build();
-
-    underTest.persist(delivery);
-
-    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
-    assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect");
-  }
-
-  @Test
-  public void purge_deletes_records_older_than_one_month_on_the_project() {
-    when(system.now()).thenReturn(NOW);
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO));
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO));
-    dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO));
-    dbSession.commit();
-
-    underTest.purge("PROJECT_1");
-
-    // do not purge another project PROJECT_2
-    assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2", "D3");
-  }
-
-  @Test
-  public void persist_effective_url_if_present() {
-    when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
-    String effectiveUrl = randomAlphabetic(15);
-    WebhookDelivery delivery = newBuilderTemplate()
-      .setEffectiveUrl(effectiveUrl)
-      .build();
-
-    underTest.persist(delivery);
-
-    WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
-    assertThat(dto.getUrl()).isEqualTo(effectiveUrl);
-  }
-
-  private static WebhookDelivery.Builder newBuilderTemplate() {
-    return new WebhookDelivery.Builder()
-      .setWebhook(new Webhook("WEBHOOK_UUID_1", "COMPONENT1", "TASK1", RandomStringUtils.randomAlphanumeric(40),"Jenkins", "http://jenkins", null))
-      .setPayload(new WebhookPayload("my-project", "{json}"))
-      .setAt(1_000_000L)
-      .setHttpStatus(200)
-      .setDurationInMs(1_000);
-  }
-
-  private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) {
-    return WebhookDeliveryTesting.newDto()
-      .setUuid(uuid)
-      .setComponentUuid(componentUuid)
-      .setCreatedAt(at);
-  }
-}
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index.xml b/server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index.xml
deleted file mode 100644 (file)
index 31b217d..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<dataset>
-  <rules id="1"
-         system_tags="[null]"
-         name="Avoid Cycles"
-         plugin_rule_key="AvoidCycles"
-         plugin_config_key="[null]"
-         plugin_name="squid"/>
-
-  <projects organization_uuid="org1"
-            uuid="THE_PROJECT"
-            uuid_path="NOT_USED"
-            project_uuid="THE_PROJECT"
-            path="[null]"
-            id="10"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="the_project"
-            name="TheProject"
-            private="[false]"/>
-  <projects organization_uuid="org1"
-            uuid="THE_FILE"
-            uuid_path="NOT_USED"
-            project_uuid="THE_PROJECT"
-            path="src/main/java/TheFile.java"
-            id="11"
-            scope="FIL"
-            qualifier="FIL"
-            kee="abcde"
-            name="TheFile"
-            private="[false]"/>
-
-  <issues id="1"
-          kee="ABCDE"
-          resolution="FIXED"
-          status="RESOLVED"
-          severity="BLOCKER"
-          manual_severity="[false]"
-          assignee="winner"
-          author_login="[null]"
-          checksum="FFFFF"
-          gap="[null]"
-          effort="[null]"
-          message="[null]"
-          line="444"
-          component_uuid="THE_FILE"
-          project_uuid="THE_PROJECT"
-          rule_id="1"
-          reporter="[null]"
-          issue_attributes="JIRA=http://jira.com"
-          action_plan_key="[null]"
-          created_at="1500000000000"
-          updated_at="1550000000000"
-          issue_creation_date="1115848800000"
-          issue_update_date="1368828000000"
-          issue_close_date="[null]"
-          locations="[null]"
-          issue_type="2"
-  />
-</dataset>
diff --git a/server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index_project.xml b/server/sonar-server-common/src/test/resources/org/sonar/server/issue/index/IssueIndexerTest/index_project.xml
deleted file mode 100644 (file)
index 2800fcc..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<dataset>
-  <rules id="1"
-         system_tags="[null]"
-         name="Avoid Cycles"
-         plugin_rule_key="AvoidCycles"
-         plugin_config_key="[null]"
-         plugin_name="squid"/>
-
-  <!-- Project 1 -->
-  <projects organization_uuid="org1"
-            uuid="THE_PROJECT_1"
-            uuid_path="NOT_USED"
-            project_uuid="THE_PROJECT_1"
-            path="[null]"
-            id="10"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="the_project_1"
-            name="TheProject1"
-            private="[false]"/>
-  <projects organization_uuid="org1"
-            uuid="THE_FILE_1"
-            uuid_path="NOT_USED"
-            project_uuid="THE_PROJECT_1"
-            path="src/main/java/TheFile.java"
-            id="11"
-            scope="FIL"
-            qualifier="FIL"
-            kee="the_file_1"
-            name="TheFile1"
-            private="[false]"/>
-
-  <issues id="1"
-          kee="ABCDE"
-          resolution="FIXED"
-          status="RESOLVED"
-          severity="BLOCKER"
-          manual_severity="[false]"
-          assignee="winner"
-          author_login="[null]"
-          checksum="FFFFF"
-          gap="[null]"
-          effort="[null]"
-          message="[null]"
-          line="444"
-          component_uuid="THE_FILE_1"
-          project_uuid="THE_PROJECT_1"
-          rule_id="1"
-          reporter="[null]"
-          issue_attributes="JIRA=http://jira.com"
-          action_plan_key="[null]"
-          created_at="1500000000000"
-          updated_at="1550000000000"
-          issue_creation_date="1115848800000"
-          issue_update_date="1368828000000"
-          issue_close_date="[null]"
-          locations="[null]"
-          issue_type="2"
-  />
-
-  <!-- Project 2 -->
-  <projects organization_uuid="org1"
-            uuid="THE_PROJECT_2"
-            uuid_path="NOT_USED"
-            path="[null]"
-            id="100"
-            scope="PRJ"
-            qualifier="TRK"
-            kee="the_project_2"
-            name="TheProject2"
-            private="[false]"/>
-  <projects organization_uuid="org1"
-            uuid="THE_FILE_2"
-            uuid_path="NOT_USED"
-            path="src/main/java/TheFile.java"
-            id="111"
-            scope="FIL"
-            qualifier="FIL"
-            kee="the_file_2"
-            name="TheFile2"
-            private="[false]"/>
-
-  <issues id="10"
-          kee="EDCBA"
-          resolution="FIXED"
-          status="RESOLVED"
-          severity="BLOCKER"
-          manual_severity="[false]"
-          assignee="winner"
-          author_login="[null]"
-          checksum="FFFFF"
-          gap="[null]"
-          effort="[null]"
-          message="[null]"
-          line="444"
-          component_uuid="THE_FILE_2"
-          project_uuid="THE_PROJECT_2"
-          rule_id="1"
-          reporter="[null]"
-          issue_attributes="JIRA=http://jira.com"
-          action_plan_key="[null]"
-          created_at="1500000000000"
-          updated_at="1550000000000"
-          issue_creation_date="1115848800000"
-          issue_update_date="1368828000000"
-          issue_close_date="[null]"
-          locations="[null]"
-          issue_type="2"
-  />
-</dataset>
diff --git a/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java b/sonar-core/src/it/java/org/sonar/core/util/DefaultHttpDownloaderIT.java
new file mode 100644 (file)
index 0000000..9e13d27
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.core.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.zip.GZIPOutputStream;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.core.Container;
+import org.simpleframework.http.core.ContainerServer;
+import org.simpleframework.transport.connect.SocketConnection;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.api.utils.SonarException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultHttpDownloaderIT {
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+
+  @Rule
+  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+  private static SocketConnection socketConnection;
+  private static String baseUrl;
+
+  @BeforeClass
+  public static void startServer() throws IOException {
+    socketConnection = new SocketConnection(new ContainerServer(new Container() {
+      public void handle(Request req, Response resp) {
+        try {
+          if (req.getPath().getPath().contains("/redirect/")) {
+            resp.setCode(303);
+            resp.setValue("Location", "/");
+          } else {
+            if (req.getPath().getPath().contains("/timeout/")) {
+              try {
+                Thread.sleep(500);
+              } catch (InterruptedException e) {
+                throw new IllegalStateException(e);
+              }
+            }
+            if (req.getPath().getPath().contains("/gzip/")) {
+              if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
+                throw new IllegalStateException("Should accept gzip");
+              }
+              resp.setValue("Content-Encoding", "gzip");
+              GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
+              gzipOutputStream.write("GZIP response".getBytes());
+              gzipOutputStream.close();
+            } else {
+              resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
+            }
+          }
+        } catch (IOException e) {
+          throw new IllegalStateException(e);
+        } finally {
+          try {
+            resp.close();
+          } catch (IOException ignored) {
+          }
+        }
+      }
+    }));
+    SocketAddress address = socketConnection.connect(new InetSocketAddress("localhost", 0));
+
+    baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
+  }
+
+  @AfterClass
+  public static void stopServer() throws IOException {
+    if (null != socketConnection) {
+      socketConnection.close();
+    }
+  }
+
+  @Test(timeout = 10000)
+  public void openStream_network_errors() throws IOException, URISyntaxException {
+    // non routable address
+    String url = "http://10.255.255.1";
+
+    assertThatThrownBy(() -> {
+      DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings().asConfig(), 10, 50000);
+      downloader.openStream(new URI(url));
+    })
+      .isInstanceOf(SonarException.class)
+      .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
+        @Override
+        public boolean matches(Object ex) {
+          return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
+        }
+
+        @Override
+        public void describeTo(Description arg0) {
+        }
+      });
+  }
+
+  @Test
+  public void downloadBytes() throws URISyntaxException {
+    byte[] bytes = new DefaultHttpDownloader(new MapSettings().asConfig()).readBytes(new URI(baseUrl));
+    assertThat(bytes.length).isGreaterThan(10);
+  }
+
+  @Test
+  public void readString() throws URISyntaxException {
+    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
+    assertThat(text.length()).isGreaterThan(10);
+  }
+
+  @Test
+  public void readGzipString() throws URISyntaxException {
+    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
+    assertThat(text).isEqualTo("GZIP response");
+  }
+
+  @Test
+  public void readStringWithDefaultTimeout() throws URISyntaxException {
+    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
+    assertThat(text.length()).isGreaterThan(10);
+  }
+
+  @Test
+  public void readStringWithTimeout() throws URISyntaxException {
+    assertThatThrownBy(() -> new DefaultHttpDownloader(new MapSettings().asConfig(), 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
+      .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
+        @Override
+        public boolean matches(Object ex) {
+          return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
+        }
+
+        @Override
+        public void describeTo(Description arg0) {
+        }
+      });
+  }
+
+  @Test
+  public void downloadToFile() throws URISyntaxException, IOException {
+    File toDir = temporaryFolder.newFolder();
+    File toFile = new File(toDir, "downloadToFile.txt");
+
+    new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
+    assertThat(toFile).exists();
+    assertThat(toFile.length()).isGreaterThan(10L);
+  }
+
+  @Test
+  public void shouldNotCreateFileIfFailToDownload() throws Exception {
+    File toDir = temporaryFolder.newFolder();
+    File toFile = new File(toDir, "downloadToFile.txt");
+
+    try {
+      int port = new InetSocketAddress("localhost", 0).getPort();
+      new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI("http://localhost:" + port), toFile);
+    } catch (SonarException e) {
+      assertThat(toFile).doesNotExist();
+    }
+  }
+
+  @Test
+  public void userAgent_includes_version_and_SERVER_ID_when_server_is_provided() throws URISyntaxException, IOException {
+    Server server = mock(Server.class);
+    when(server.getVersion()).thenReturn("2.2");
+    MapSettings settings = new MapSettings();
+    settings.setProperty(CoreProperties.SERVER_ID, "blablabla");
+
+    InputStream stream = new DefaultHttpDownloader(server, settings.asConfig()).openStream(new URI(baseUrl));
+    Properties props = new Properties();
+    props.load(stream);
+    stream.close();
+
+    assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 # blablabla");
+  }
+
+  @Test
+  public void userAgent_includes_only_version_when_there_is_no_SERVER_ID_and_server_is_provided() throws URISyntaxException, IOException {
+    Server server = mock(Server.class);
+    when(server.getVersion()).thenReturn("2.2");
+
+    InputStream stream = new DefaultHttpDownloader(server, new MapSettings().asConfig()).openStream(new URI(baseUrl));
+    Properties props = new Properties();
+    props.load(stream);
+    stream.close();
+
+    assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
+  }
+
+  @Test
+  public void userAgent_is_static_value_when_server_is_not_provided() throws URISyntaxException, IOException {
+    InputStream stream = new DefaultHttpDownloader(new MapSettings().asConfig()).openStream(new URI(baseUrl));
+    Properties props = new Properties();
+    props.load(stream);
+    stream.close();
+
+    assertThat(props.getProperty("agent")).isEqualTo("SonarQube");
+  }
+
+  @Test
+  public void followRedirect() throws URISyntaxException {
+    String content = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
+    assertThat(content).contains("agent");
+  }
+
+  @Test
+  public void shouldGetDirectProxySynthesis() throws URISyntaxException {
+    ProxySelector proxySelector = mock(ProxySelector.class);
+    when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(Proxy.NO_PROXY));
+    assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("no proxy");
+  }
+
+  @Test
+  public void shouldGetProxySynthesis() throws URISyntaxException {
+    ProxySelector proxySelector = mock(ProxySelector.class);
+    when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(new FakeProxy()));
+    assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("HTTP proxy: /123.45.67.89:4040");
+  }
+
+  @Test
+  public void supported_schemes() {
+    assertThat(new DefaultHttpDownloader(new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
+  }
+
+  @Test
+  public void uri_description() throws URISyntaxException {
+    String description = new DefaultHttpDownloader(new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
+    assertThat(description).matches("http://sonarsource.org \\(.*\\)");
+  }
+
+  @Test
+  public void configure_http_proxy_credentials() {
+    DefaultHttpDownloader.AuthenticatorFacade system = mock(DefaultHttpDownloader.AuthenticatorFacade.class);
+    MapSettings settings = new MapSettings();
+    settings.setProperty("https.proxyHost", "1.2.3.4");
+    settings.setProperty("http.proxyUser", "the_login");
+    settings.setProperty("http.proxyPassword", "the_passwd");
+
+    new DefaultHttpDownloader.BaseHttpDownloader(system, settings.asConfig(), null);
+
+    verify(system).setDefaultAuthenticator(argThat(authenticator -> {
+      DefaultHttpDownloader.ProxyAuthenticator a = (DefaultHttpDownloader.ProxyAuthenticator) authenticator;
+      PasswordAuthentication authentication = a.getPasswordAuthentication();
+      return authentication.getUserName().equals("the_login") &&
+        new String(authentication.getPassword()).equals("the_passwd");
+    }));
+  }
+
+  private static class FakeProxy extends Proxy {
+    FakeProxy() {
+      super(Type.HTTP, new InetSocketAddress("123.45.67.89", 4040));
+    }
+  }
+}
diff --git a/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java b/sonar-core/src/test/java/org/sonar/core/util/DefaultHttpDownloaderTest.java
deleted file mode 100644 (file)
index e8d5a17..0000000
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.PasswordAuthentication;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.SocketAddress;
-import java.net.SocketTimeoutException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.zip.GZIPOutputStream;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.simpleframework.http.Request;
-import org.simpleframework.http.Response;
-import org.simpleframework.http.core.Container;
-import org.simpleframework.http.core.ContainerServer;
-import org.simpleframework.transport.connect.SocketConnection;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.Server;
-import org.sonar.api.utils.SonarException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultHttpDownloaderTest {
-
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-
-  @Rule
-  public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
-  private static SocketConnection socketConnection;
-  private static String baseUrl;
-
-  @BeforeClass
-  public static void startServer() throws IOException {
-    socketConnection = new SocketConnection(new ContainerServer(new Container() {
-      public void handle(Request req, Response resp) {
-        try {
-          if (req.getPath().getPath().contains("/redirect/")) {
-            resp.setCode(303);
-            resp.setValue("Location", "/");
-          } else {
-            if (req.getPath().getPath().contains("/timeout/")) {
-              try {
-                Thread.sleep(500);
-              } catch (InterruptedException e) {
-                throw new IllegalStateException(e);
-              }
-            }
-            if (req.getPath().getPath().contains("/gzip/")) {
-              if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
-                throw new IllegalStateException("Should accept gzip");
-              }
-              resp.setValue("Content-Encoding", "gzip");
-              GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
-              gzipOutputStream.write("GZIP response".getBytes());
-              gzipOutputStream.close();
-            } else {
-              resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
-            }
-          }
-        } catch (IOException e) {
-          throw new IllegalStateException(e);
-        } finally {
-          try {
-            resp.close();
-          } catch (IOException ignored) {
-          }
-        }
-      }
-    }));
-    SocketAddress address = socketConnection.connect(new InetSocketAddress("localhost", 0));
-
-    baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
-  }
-
-  @AfterClass
-  public static void stopServer() throws IOException {
-    if (null != socketConnection) {
-      socketConnection.close();
-    }
-  }
-
-  @Test(timeout = 10000)
-  public void openStream_network_errors() throws IOException, URISyntaxException {
-    // non routable address
-    String url = "http://10.255.255.1";
-
-    assertThatThrownBy(() -> {
-      DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings().asConfig(), 10, 50000);
-      downloader.openStream(new URI(url));
-    })
-      .isInstanceOf(SonarException.class)
-      .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
-        @Override
-        public boolean matches(Object ex) {
-          return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
-        }
-
-        @Override
-        public void describeTo(Description arg0) {
-        }
-      });
-  }
-
-  @Test
-  public void downloadBytes() throws URISyntaxException {
-    byte[] bytes = new DefaultHttpDownloader(new MapSettings().asConfig()).readBytes(new URI(baseUrl));
-    assertThat(bytes.length).isGreaterThan(10);
-  }
-
-  @Test
-  public void readString() throws URISyntaxException {
-    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
-    assertThat(text.length()).isGreaterThan(10);
-  }
-
-  @Test
-  public void readGzipString() throws URISyntaxException {
-    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
-    assertThat(text).isEqualTo("GZIP response");
-  }
-
-  @Test
-  public void readStringWithDefaultTimeout() throws URISyntaxException {
-    String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
-    assertThat(text.length()).isGreaterThan(10);
-  }
-
-  @Test
-  public void readStringWithTimeout() throws URISyntaxException {
-    assertThatThrownBy(() -> new DefaultHttpDownloader(new MapSettings().asConfig(), 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
-      .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
-        @Override
-        public boolean matches(Object ex) {
-          return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
-        }
-
-        @Override
-        public void describeTo(Description arg0) {
-        }
-      });
-  }
-
-  @Test
-  public void downloadToFile() throws URISyntaxException, IOException {
-    File toDir = temporaryFolder.newFolder();
-    File toFile = new File(toDir, "downloadToFile.txt");
-
-    new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
-    assertThat(toFile).exists();
-    assertThat(toFile.length()).isGreaterThan(10L);
-  }
-
-  @Test
-  public void shouldNotCreateFileIfFailToDownload() throws Exception {
-    File toDir = temporaryFolder.newFolder();
-    File toFile = new File(toDir, "downloadToFile.txt");
-
-    try {
-      int port = new InetSocketAddress("localhost", 0).getPort();
-      new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI("http://localhost:" + port), toFile);
-    } catch (SonarException e) {
-      assertThat(toFile).doesNotExist();
-    }
-  }
-
-  @Test
-  public void userAgent_includes_version_and_SERVER_ID_when_server_is_provided() throws URISyntaxException, IOException {
-    Server server = mock(Server.class);
-    when(server.getVersion()).thenReturn("2.2");
-    MapSettings settings = new MapSettings();
-    settings.setProperty(CoreProperties.SERVER_ID, "blablabla");
-
-    InputStream stream = new DefaultHttpDownloader(server, settings.asConfig()).openStream(new URI(baseUrl));
-    Properties props = new Properties();
-    props.load(stream);
-    stream.close();
-
-    assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 # blablabla");
-  }
-
-  @Test
-  public void userAgent_includes_only_version_when_there_is_no_SERVER_ID_and_server_is_provided() throws URISyntaxException, IOException {
-    Server server = mock(Server.class);
-    when(server.getVersion()).thenReturn("2.2");
-
-    InputStream stream = new DefaultHttpDownloader(server, new MapSettings().asConfig()).openStream(new URI(baseUrl));
-    Properties props = new Properties();
-    props.load(stream);
-    stream.close();
-
-    assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
-  }
-
-  @Test
-  public void userAgent_is_static_value_when_server_is_not_provided() throws URISyntaxException, IOException {
-    InputStream stream = new DefaultHttpDownloader(new MapSettings().asConfig()).openStream(new URI(baseUrl));
-    Properties props = new Properties();
-    props.load(stream);
-    stream.close();
-
-    assertThat(props.getProperty("agent")).isEqualTo("SonarQube");
-  }
-
-  @Test
-  public void followRedirect() throws URISyntaxException {
-    String content = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
-    assertThat(content).contains("agent");
-  }
-
-  @Test
-  public void shouldGetDirectProxySynthesis() throws URISyntaxException {
-    ProxySelector proxySelector = mock(ProxySelector.class);
-    when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(Proxy.NO_PROXY));
-    assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("no proxy");
-  }
-
-  @Test
-  public void shouldGetProxySynthesis() throws URISyntaxException {
-    ProxySelector proxySelector = mock(ProxySelector.class);
-    when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(new FakeProxy()));
-    assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("HTTP proxy: /123.45.67.89:4040");
-  }
-
-  @Test
-  public void supported_schemes() {
-    assertThat(new DefaultHttpDownloader(new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
-  }
-
-  @Test
-  public void uri_description() throws URISyntaxException {
-    String description = new DefaultHttpDownloader(new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
-    assertThat(description).matches("http://sonarsource.org \\(.*\\)");
-  }
-
-  @Test
-  public void configure_http_proxy_credentials() {
-    DefaultHttpDownloader.AuthenticatorFacade system = mock(DefaultHttpDownloader.AuthenticatorFacade.class);
-    MapSettings settings = new MapSettings();
-    settings.setProperty("https.proxyHost", "1.2.3.4");
-    settings.setProperty("http.proxyUser", "the_login");
-    settings.setProperty("http.proxyPassword", "the_passwd");
-
-    new DefaultHttpDownloader.BaseHttpDownloader(system, settings.asConfig(), null);
-
-    verify(system).setDefaultAuthenticator(argThat(authenticator -> {
-      DefaultHttpDownloader.ProxyAuthenticator a = (DefaultHttpDownloader.ProxyAuthenticator) authenticator;
-      PasswordAuthentication authentication = a.getPasswordAuthentication();
-      return authentication.getUserName().equals("the_login") &&
-        new String(authentication.getPassword()).equals("the_passwd");
-    }));
-  }
-
-  private static class FakeProxy extends Proxy {
-    FakeProxy() {
-      super(Type.HTTP, new InetSocketAddress("123.45.67.89", 4040));
-    }
-  }
-}