--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+/*
+ * 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
+ };
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+
+}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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/><h2>Ask Yourself Whether</h2><br/>bar<br/>"
+ + "<h2>Recommended Secure Coding Practices</h2><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}
+ };
+ }
+
+}
--- /dev/null
+/*
+ * 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));
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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());
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+<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>
--- /dev/null
+<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>
+++ /dev/null
-/*
- * 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());
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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");
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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());
- }
-}
+++ /dev/null
-/*
- * 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
- };
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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");
- }
-
-}
+++ /dev/null
-/*
- * 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());
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
+++ /dev/null
-/*
- * 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/><h2>Ask Yourself Whether</h2><br/>bar<br/>"
- + "<h2>Recommended Secure Coding Practices</h2><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}
- };
- }
-
-}
+++ /dev/null
-/*
- * 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));
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-}
+++ /dev/null
-/*
- * 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());
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
-}
+++ /dev/null
-<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>
+++ /dev/null
-<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>
--- /dev/null
+/*
+ * 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));
+ }
+ }
+}
+++ /dev/null
-/*
- * 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));
- }
- }
-}