aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>2017-04-18 10:58:02 +0200
committerDaniel Schwarz <bartfastiel@users.noreply.github.com>2017-04-20 09:48:52 +0200
commit027e5416b7556ccd3bccce80f91b93b5d1812f0a (patch)
treee7ae3c1e41211a13efeaa344e734c5e0d186e567 /server
parentaf7327e59b88d1a7738b28bad169941fdb2845f6 (diff)
downloadsonarqube-027e5416b7556ccd3bccce80f91b93b5d1812f0a.tar.gz
sonarqube-027e5416b7556ccd3bccce80f91b93b5d1812f0a.zip
SONAR-9079 score favorite components higher in suggestions
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java18
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java66
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java41
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java27
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java58
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java23
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java40
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java58
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java31
17 files changed, 320 insertions, 100 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java
index 5ee71624704..f23a1c0094c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndex.java
@@ -22,7 +22,6 @@ package org.sonar.server.component.index;
import com.google.common.annotations.VisibleForTesting;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import org.elasticsearch.action.search.SearchRequestBuilder;
@@ -38,7 +37,6 @@ import org.elasticsearch.search.aggregations.bucket.filters.InternalFilters.Buck
import org.elasticsearch.search.aggregations.metrics.tophits.InternalTopHits;
import org.elasticsearch.search.aggregations.metrics.tophits.TopHitsBuilder;
import org.elasticsearch.search.highlight.HighlightBuilder;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.es.EsClient;
import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
import org.sonar.server.es.textsearch.ComponentTextSearchQueryFactory;
@@ -79,15 +77,15 @@ public class ComponentIndex {
return field;
}
- public List<ComponentHitsPerQualifier> search(ComponentIndexQuery query) {
+ public ComponentIndexResults search(ComponentIndexQuery query) {
return search(query, ComponentTextSearchFeature.values());
}
@VisibleForTesting
- List<ComponentHitsPerQualifier> search(ComponentIndexQuery query, ComponentTextSearchFeature... features) {
+ ComponentIndexResults search(ComponentIndexQuery query, ComponentTextSearchFeature... features) {
Collection<String> qualifiers = query.getQualifiers();
if (qualifiers.isEmpty()) {
- return Collections.emptyList();
+ return ComponentIndexResults.newBuilder().build();
}
SearchRequestBuilder request = client
@@ -128,16 +126,18 @@ public class ComponentIndex {
.setFieldKey(FIELD_KEY)
.setFieldName(FIELD_NAME)
.setRecentlyBrowsedKeys(query.getRecentlyBrowsedKeys())
+ .setFavoriteKeys(query.getFavoriteKeys())
.build();
return esQuery.must(ComponentTextSearchQueryFactory.createQuery(componentTextSearchQuery, features));
}
- private static List<ComponentHitsPerQualifier> aggregationsToQualifiers(SearchResponse response) {
+ private static ComponentIndexResults aggregationsToQualifiers(SearchResponse response) {
InternalFilters filtersAgg = response.getAggregations().get(FILTERS_AGGREGATION_NAME);
List<Bucket> buckets = filtersAgg.getBuckets();
- return buckets.stream()
- .map(ComponentIndex::bucketToQualifier)
- .collect(MoreCollectors.toList(buckets.size()));
+ return ComponentIndexResults.newBuilder()
+ .setQualifiers(
+ buckets.stream().map(ComponentIndex::bucketToQualifier))
+ .build();
}
private static ComponentHitsPerQualifier bucketToQualifier(Bucket bucket) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java
index 1c2f05c3006..806a7ba796e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexQuery.java
@@ -34,6 +34,7 @@ public class ComponentIndexQuery {
private final String query;
private final Collection<String> qualifiers;
private final Set<String> recentlyBrowsedKeys;
+ private final Set<String> favoriteKeys;
@CheckForNull
private final Integer limit;
@@ -41,6 +42,7 @@ public class ComponentIndexQuery {
this.query = requireNonNull(builder.query);
this.qualifiers = requireNonNull(builder.qualifiers);
this.recentlyBrowsedKeys = requireNonNull(builder.recentlyBrowsedKeys);
+ this.favoriteKeys = requireNonNull(builder.favoriteKeys);
this.limit = builder.limit;
}
@@ -64,10 +66,15 @@ public class ComponentIndexQuery {
return new Builder();
}
+ public Set<String> getFavoriteKeys() {
+ return favoriteKeys;
+ }
+
public static class Builder {
private String query;
private Collection<String> qualifiers = Collections.emptyList();
private Set<String> recentlyBrowsedKeys = Collections.emptySet();
+ private Set<String> favoriteKeys = Collections.emptySet();
private Integer limit;
private Builder() {
@@ -89,6 +96,11 @@ public class ComponentIndexQuery {
return this;
}
+ public Builder setFavoriteKeys(Set<String> favoriteKeys) {
+ this.favoriteKeys = Collections.unmodifiableSet(favoriteKeys);
+ return this;
+ }
+
public Builder setLimit(@Nullable Integer limit) {
checkArgument(limit == null || limit > 0, "Limit has to be strictly positive: %s", limit);
this.limit = limit;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java
new file mode 100644
index 00000000000..06234cc10c5
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexResults.java
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Collections.emptyList;
+import static java.util.Objects.requireNonNull;
+
+public class ComponentIndexResults {
+
+ private final List<ComponentHitsPerQualifier> qualifiers;
+
+ private ComponentIndexResults(Builder builder) {
+ this.qualifiers = requireNonNull(builder.qualifiers);
+ }
+
+ public Stream<ComponentHitsPerQualifier> getQualifiers() {
+ return qualifiers.stream();
+ }
+
+ public boolean isEmpty() {
+ return qualifiers.isEmpty();
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private List<ComponentHitsPerQualifier> qualifiers = emptyList();
+
+ private Builder() {
+ }
+
+ public Builder setQualifiers(Stream<ComponentHitsPerQualifier> qualifiers) {
+ this.qualifiers = qualifiers.collect(Collectors.toList());
+ return this;
+ }
+
+ public ComponentIndexResults build() {
+ return new ComponentIndexResults(this);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
index fa07c2e8920..7b423940673 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
@@ -30,7 +30,6 @@ import org.apache.commons.lang.BooleanUtils;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.RequestHandler;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
@@ -51,7 +50,7 @@ import static com.google.common.collect.Lists.newArrayList;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-public class AppAction implements RequestHandler {
+public class AppAction implements ComponentsWsAction {
private static final String PARAM_COMPONENT_ID = "componentId";
private static final String PARAM_COMPONENT = "component";
@@ -71,7 +70,8 @@ public class AppAction implements RequestHandler {
this.componentFinder = componentFinder;
}
- void define(WebService.NewController controller) {
+ @Override
+ public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("app")
.setDescription("Coverage data required for rendering the component viewer.<br>" +
"Requires the following permission: 'Browse'.")
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java
index 0ff209740c6..907b7159250 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java
@@ -19,17 +19,16 @@
*/
package org.sonar.server.component.ws;
+import java.util.Arrays;
import org.sonar.api.server.ws.WebService;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.CONTROLLER_COMPONENTS;
public class ComponentsWs implements WebService {
- private final AppAction appAction;
private final ComponentsWsAction[] actions;
- public ComponentsWs(AppAction appAction, ComponentsWsAction... actions) {
- this.appAction = appAction;
+ public ComponentsWs(ComponentsWsAction... actions) {
this.actions = actions;
}
@@ -40,10 +39,8 @@ public class ComponentsWs implements WebService {
.setDescription("Get information about a component (file, directory, project, ...) and its ancestors or descendants. " +
"Update a project or module key.");
- for (ComponentsWsAction action : actions) {
- action.define(controller);
- }
- appAction.define(controller);
+ Arrays.stream(actions)
+ .forEach(action -> action.define(controller));
controller.done();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
index 8db06733b45..244fb4f956e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SuggestionsAction.java
@@ -40,7 +40,9 @@ import org.sonar.server.component.index.ComponentHit;
import org.sonar.server.component.index.ComponentHitsPerQualifier;
import org.sonar.server.component.index.ComponentIndex;
import org.sonar.server.component.index.ComponentIndexQuery;
+import org.sonar.server.component.index.ComponentIndexResults;
import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+import org.sonar.server.favorite.FavoriteFinder;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Category;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Project;
@@ -71,12 +73,14 @@ public class SuggestionsAction implements ComponentsWsAction {
static final int EXTENDED_LIMIT = 20;
private final ComponentIndex index;
+ private final FavoriteFinder favoriteFinder;
private DbClient dbClient;
- public SuggestionsAction(DbClient dbClient, ComponentIndex index) {
+ public SuggestionsAction(DbClient dbClient, ComponentIndex index, FavoriteFinder favoriteFinder) {
this.dbClient = dbClient;
this.index = index;
+ this.favoriteFinder = favoriteFinder;
}
@Override
@@ -120,13 +124,17 @@ public class SuggestionsAction implements ComponentsWsAction {
String more = wsRequest.param(PARAM_MORE);
List<String> recentlyBrowsedParam = wsRequest.paramAsStrings(PARAM_RECENTLY_BROWSED);
Set<String> recentlyBrowsedKeys = recentlyBrowsedParam == null ? emptySet() : copyOf(recentlyBrowsedParam);
+ Set<String> favoriteKeys = favoriteFinder.list().stream().map(ComponentDto::getKey).collect(Collectors.toSet());
- ComponentIndexQuery.Builder queryBuilder = ComponentIndexQuery.builder().setQuery(query).setRecentlyBrowsedKeys(recentlyBrowsedKeys);
+ ComponentIndexQuery.Builder queryBuilder = ComponentIndexQuery.builder()
+ .setQuery(query)
+ .setRecentlyBrowsedKeys(recentlyBrowsedKeys)
+ .setFavoriteKeys(favoriteKeys);
- List<ComponentHitsPerQualifier> componentsPerQualifiers = getComponentsPerQualifiers(more, queryBuilder);
+ ComponentIndexResults componentsPerQualifiers = getComponentsPerQualifiers(more, queryBuilder);
String warning = getWarning(query);
- SuggestionsWsResponse searchWsResponse = toResponse(componentsPerQualifiers, recentlyBrowsedKeys, warning);
+ SuggestionsWsResponse searchWsResponse = toResponse(componentsPerQualifiers, recentlyBrowsedKeys, favoriteKeys, warning);
writeProtobuf(searchWsResponse, wsRequest, wsResponse);
}
@@ -138,8 +146,7 @@ public class SuggestionsAction implements ComponentsWsAction {
return null;
}
- private List<ComponentHitsPerQualifier> getComponentsPerQualifiers(String more, ComponentIndexQuery.Builder queryBuilder) {
- List<ComponentHitsPerQualifier> componentsPerQualifiers;
+ private ComponentIndexResults getComponentsPerQualifiers(@Nullable String more, ComponentIndexQuery.Builder queryBuilder) {
if (more == null) {
queryBuilder.setQualifiers(stream(SuggestionCategory.values()).map(SuggestionCategory::getQualifier).collect(Collectors.toList()))
.setLimit(DEFAULT_LIMIT);
@@ -147,22 +154,21 @@ public class SuggestionsAction implements ComponentsWsAction {
queryBuilder.setQualifiers(singletonList(SuggestionCategory.getByName(more).getQualifier()))
.setLimit(EXTENDED_LIMIT);
}
- componentsPerQualifiers = searchInIndex(queryBuilder.build());
- return componentsPerQualifiers;
+ return searchInIndex(queryBuilder.build());
}
- private List<ComponentHitsPerQualifier> searchInIndex(ComponentIndexQuery componentIndexQuery) {
+ private ComponentIndexResults searchInIndex(ComponentIndexQuery componentIndexQuery) {
return index.search(componentIndexQuery);
}
- private SuggestionsWsResponse toResponse(List<ComponentHitsPerQualifier> componentsPerQualifiers, Set<String> recentlyBrowsedKeys, @Nullable String warning) {
+ private SuggestionsWsResponse toResponse(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys, @Nullable String warning) {
SuggestionsWsResponse.Builder builder = newBuilder();
if (!componentsPerQualifiers.isEmpty()) {
Map<String, OrganizationDto> organizationsByUuids;
Map<String, ComponentDto> componentsByUuids;
Map<String, ComponentDto> projectsByUuids;
try (DbSession dbSession = dbClient.openSession(false)) {
- Set<String> componentUuids = componentsPerQualifiers.stream()
+ Set<String> componentUuids = componentsPerQualifiers.getQualifiers()
.map(ComponentHitsPerQualifier::getHits)
.flatMap(Collection::stream)
.map(ComponentHit::getUuid)
@@ -182,7 +188,7 @@ public class SuggestionsAction implements ComponentsWsAction {
.collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
}
builder
- .addAllSuggestions(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, componentsByUuids, organizationsByUuids, projectsByUuids))
+ .addAllSuggestions(toCategories(componentsPerQualifiers, recentlyBrowsedKeys, favoriteKeys, componentsByUuids, organizationsByUuids, projectsByUuids))
.addAllOrganizations(toOrganizations(organizationsByUuids))
.addAllProjects(toProjects(projectsByUuids));
}
@@ -190,12 +196,12 @@ public class SuggestionsAction implements ComponentsWsAction {
return builder.build();
}
- private static List<Category> toCategories(List<ComponentHitsPerQualifier> componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Map<String, ComponentDto> componentsByUuids,
- Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
- return componentsPerQualifiers.stream().map(qualifier -> {
+ private static List<Category> toCategories(ComponentIndexResults componentsPerQualifiers, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys,
+ Map<String, ComponentDto> componentsByUuids, Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
+ return componentsPerQualifiers.getQualifiers().map(qualifier -> {
List<Suggestion> suggestions = qualifier.getHits().stream()
- .map(hit -> toSuggestion(hit, recentlyBrowsedKeys, componentsByUuids, organizationByUuids, projectsByUuids))
+ .map(hit -> toSuggestion(hit, recentlyBrowsedKeys, favoriteKeys, componentsByUuids, organizationByUuids, projectsByUuids))
.collect(toList());
return Category.newBuilder()
@@ -206,7 +212,7 @@ public class SuggestionsAction implements ComponentsWsAction {
}).collect(toList());
}
- private static Suggestion toSuggestion(ComponentHit hit, Set<String> recentlyBrowsedKeys, Map<String, ComponentDto> componentsByUuids,
+ private static Suggestion toSuggestion(ComponentHit hit, Set<String> recentlyBrowsedKeys, Set<String> favoriteKeys, Map<String, ComponentDto> componentsByUuids,
Map<String, OrganizationDto> organizationByUuids, Map<String, ComponentDto> projectsByUuids) {
ComponentDto result = componentsByUuids.get(hit.getUuid());
String organizationKey = organizationByUuids.get(result.getOrganizationUuid()).getKey();
@@ -219,6 +225,7 @@ public class SuggestionsAction implements ComponentsWsAction {
.setName(result.longName())
.setMatch(hit.getHighlightedText().orElse(HtmlEscapers.htmlEscaper().escape(result.longName())))
.setIsRecentlyBrowsed(recentlyBrowsedKeys.contains(result.getKey()))
+ .setIsFavorite(favoriteKeys.contains(result.getKey()))
.build();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java b/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java
index 86aa7e9b57e..980bc088332 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchFeature.java
@@ -21,6 +21,7 @@ package org.sonar.server.es.textsearch;
import java.util.Arrays;
import java.util.Locale;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
@@ -83,12 +84,32 @@ public enum ComponentTextSearchFeature {
},
RECENTLY_BROWSED {
@Override
- public QueryBuilder getQuery(ComponentTextSearchQuery query) {
- return termsQuery(query.getFieldKey(), query.getRecentlyBrowsedKeys()).boost(100f);
+ public Stream<QueryBuilder> getQueries(ComponentTextSearchQuery query) {
+ Set<String> recentlyBrowsedKeys = query.getRecentlyBrowsedKeys();
+ if (recentlyBrowsedKeys.isEmpty()) {
+ return Stream.empty();
+ }
+ return Stream.of(termsQuery(query.getFieldKey(), recentlyBrowsedKeys).boost(100f));
+ }
+ },
+ FAVORITE {
+ @Override
+ public Stream<QueryBuilder> getQueries(ComponentTextSearchQuery query) {
+ Set<String> favoriteKeys = query.getFavoriteKeys();
+ if (favoriteKeys.isEmpty()) {
+ return Stream.empty();
+ }
+ return Stream.of(termsQuery(query.getFieldKey(), favoriteKeys).boost(1000f));
}
};
- public abstract QueryBuilder getQuery(ComponentTextSearchQuery query);
+ public Stream<QueryBuilder> getQueries(ComponentTextSearchQuery query) {
+ return Stream.of(getQuery(query));
+ }
+
+ public QueryBuilder getQuery(ComponentTextSearchQuery query) {
+ throw new UnsupportedOperationException();
+ }
public static Stream<String> split(String queryText) {
return Arrays.stream(
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java
index 46c7ec84f4e..6a205fbdd73 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java
@@ -45,7 +45,7 @@ public class ComponentTextSearchQueryFactory {
checkArgument(features.length > 0, "features cannot be empty");
BoolQueryBuilder featureQuery = boolQuery();
Arrays.stream(features)
- .map(f -> f.getQuery(query))
+ .flatMap(f -> f.getQueries(query))
.forEach(featureQuery::should);
return featureQuery;
}
@@ -55,12 +55,14 @@ public class ComponentTextSearchQueryFactory {
private final String fieldKey;
private final String fieldName;
private final Set<String> recentlyBrowsedKeys;
+ private final Set<String> favoriteKeys;
private ComponentTextSearchQuery(Builder builder) {
this.queryText = builder.queryText;
this.fieldKey = builder.fieldKey;
this.fieldName = builder.fieldName;
this.recentlyBrowsedKeys = builder.recentlyBrowsedKeys;
+ this.favoriteKeys = builder.favoriteKeys;
}
public String getQueryText() {
@@ -83,11 +85,16 @@ public class ComponentTextSearchQueryFactory {
return new Builder();
}
+ public Set<String> getFavoriteKeys() {
+ return favoriteKeys;
+ }
+
public static class Builder {
private String queryText;
private String fieldKey;
private String fieldName;
private Set<String> recentlyBrowsedKeys = Collections.emptySet();
+ private Set<String> favoriteKeys = Collections.emptySet();
/**
* The text search query
@@ -121,11 +128,20 @@ public class ComponentTextSearchQueryFactory {
return this;
}
+ /**
+ * Component keys of favorite items
+ */
+ public Builder setFavoriteKeys(Set<String> favoriteKeys) {
+ this.favoriteKeys = ImmutableSet.copyOf(favoriteKeys);
+ return this;
+ }
+
public ComponentTextSearchQuery build() {
this.queryText = requireNonNull(queryText, "query text cannot be null");
this.fieldKey = requireNonNull(fieldKey, "field key cannot be null");
this.fieldName = requireNonNull(fieldName, "field name cannot be null");
this.recentlyBrowsedKeys = requireNonNull(recentlyBrowsedKeys, "field recentlyBrowsedKeys cannot be null");
+ this.favoriteKeys = requireNonNull(favoriteKeys, "field favoriteKeys cannot be null");
return new ComponentTextSearchQuery(this);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java
index 7f444b064fe..56b82d6730d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java
@@ -33,7 +33,7 @@ public class ComponentIndexCombinationTest extends ComponentIndexTest {
public void return_empty_list_if_no_fields_match_query() {
indexProject("struts", "Apache Struts");
- assertThat(index.search(ComponentIndexQuery.builder().setQuery("missing").build())).isEmpty();
+ assertThat(index.search(ComponentIndexQuery.builder().setQuery("missing").build()).isEmpty()).isTrue();
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java
new file mode 100644
index 00000000000..76fa97b3724
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureFavoriteTest.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static java.util.Collections.singletonList;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
+public class ComponentIndexFeatureFavoriteTest extends ComponentIndexTest {
+
+ @Before
+ public void before() {
+ features.set(ComponentTextSearchFeature.PREFIX, ComponentTextSearchFeature.FAVORITE);
+ }
+
+ @Test
+ public void scoring_cares_about_favorites() {
+ ComponentDto project1 = indexProject("sonarqube", "SonarQube");
+ ComponentDto project2 = indexProject("recent", "SonarQube Recently");
+
+ ComponentIndexQuery query1 = ComponentIndexQuery.builder()
+ .setQuery("SonarQube")
+ .setQualifiers(singletonList(PROJECT))
+ .setFavoriteKeys(of(project1.getKey()))
+ .build();
+ assertSearch(query1).containsExactly(uuids(project1, project2));
+
+ ComponentIndexQuery query2 = ComponentIndexQuery.builder()
+ .setQuery("SonarQube")
+ .setQualifiers(singletonList(PROJECT))
+ .setFavoriteKeys(of(project2.getKey()))
+ .build();
+ assertSearch(query2).containsExactly(uuids(project2, project1));
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java
index 5ab1ae5561a..e6f33e594c1 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexFeatureRecentlyBrowsedTest.java
@@ -33,7 +33,7 @@ public class ComponentIndexFeatureRecentlyBrowsedTest extends ComponentIndexTest
@Before
public void before() {
- features.set(ComponentTextSearchFeature.PREFIX, ComponentTextSearchFeature.EXACT_IGNORE_CASE, ComponentTextSearchFeature.RECENTLY_BROWSED);
+ features.set(ComponentTextSearchFeature.PREFIX, ComponentTextSearchFeature.RECENTLY_BROWSED);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
index 1f0c14fa483..97f81c171eb 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java
@@ -20,8 +20,8 @@
package org.sonar.server.component.index;
import java.util.Collections;
-import java.util.List;
import java.util.Optional;
+import java.util.stream.Stream;
import org.junit.Test;
import org.sonar.api.resources.Qualifiers;
@@ -66,7 +66,7 @@ public class ComponentIndexHighlightTest extends ComponentIndexTest {
.setQuery(search)
.setQualifiers(Collections.singletonList(Qualifiers.FILE))
.build();
- List<ComponentHitsPerQualifier> results = index.search(query, features.get());
+ Stream<ComponentHitsPerQualifier> results = index.search(query, features.get()).getQualifiers();
assertThat(results).flatExtracting(ComponentHitsPerQualifier::getHits)
.extracting(ComponentHit::getHighlightedText)
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java
index bad8ea15400..6cc39ae4877 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexScoreTest.java
@@ -19,11 +19,17 @@
*/
package org.sonar.server.component.index;
+import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.server.es.textsearch.ComponentTextSearchFeature;
+import static java.util.Arrays.asList;
+import static org.sonar.api.resources.Qualifiers.FILE;
+import static org.sonar.api.resources.Qualifiers.MODULE;
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+
public class ComponentIndexScoreTest extends ComponentIndexTest {
@Test
@@ -105,6 +111,23 @@ public class ComponentIndexScoreTest extends ComponentIndexTest {
}
@Test
+ public void should_prefer_favorite_over_recently_browsed() {
+ ComponentDto recentlyBrowsed = db.components().insertProject(c -> c.setName("File1"));
+ index(recentlyBrowsed);
+
+ ComponentDto favorite = db.components().insertProject(c -> c.setName("File2"));
+ index(favorite);
+
+ ComponentIndexQuery query = ComponentIndexQuery.builder()
+ .setQuery("sonarqube")
+ .setQualifiers(asList(PROJECT, MODULE, FILE))
+ .setRecentlyBrowsedKeys(ImmutableSet.of(recentlyBrowsed.getKey()))
+ .setFavoriteKeys(ImmutableSet.of(favorite.getKey()))
+ .build();
+ assertSearch(query).containsExactly(uuids(favorite, recentlyBrowsed));
+ }
+
+ @Test
public void do_not_match_wrong_file_extension() {
ComponentDto file1 = indexFile("MyClass.java");
ComponentDto file2 = indexFile("ClassExample.java");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java
index b483f6c7752..f333da67d33 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexTest.java
@@ -60,7 +60,6 @@ public abstract class ComponentIndexTest {
public ComponentTextSearchFeatureRule features = new ComponentTextSearchFeatureRule();
protected ComponentIndexer indexer = new ComponentIndexer(db.getDbClient(), es.client());
-
protected ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSession));
protected PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, indexer);
private OrganizationDto organization;
@@ -103,7 +102,7 @@ public abstract class ComponentIndexTest {
}
protected AbstractListAssert<?, ? extends List<? extends String>, String> assertSearch(ComponentIndexQuery query) {
- return assertThat(index.search(query, features.get()))
+ return assertThat(index.search(query, features.get()).getQualifiers())
.flatExtracting(ComponentHitsPerQualifier::getHits)
.extracting(ComponentHit::getUuid);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java
index c978c2d2042..347565c86da 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/AppActionTest.java
@@ -25,6 +25,7 @@ import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbTester;
@@ -38,9 +39,11 @@ import org.sonar.db.metric.MetricDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.startup.RegisterMetrics;
import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.ws.WsTester;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.test.JsonAssert;
-import static org.mockito.Mockito.mock;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.measures.CoreMetrics.COVERAGE_KEY;
import static org.sonar.api.measures.CoreMetrics.DUPLICATED_LINES_DENSITY_KEY;
import static org.sonar.api.measures.CoreMetrics.LINES_KEY;
@@ -67,15 +70,12 @@ public class AppActionTest {
@Rule
public DbTester dbTester = DbTester.create(System2.INSTANCE);
- private WsTester wsTester;
+ private AppAction underTest = new AppAction(dbTester.getDbClient(), userSessionRule, new ComponentFinder(dbTester.getDbClient()));
+ private WsActionTester wsTester = new WsActionTester(underTest);
@Before
public void setUp() {
insertMetrics();
- wsTester = new WsTester(
- new ComponentsWs(
- new AppAction(dbTester.getDbClient(), userSessionRule, new ComponentFinder(dbTester.getDbClient())),
- mock(SuggestionsAction.class)));
}
@Test
@@ -84,8 +84,8 @@ public class AppActionTest {
dbTester.commit();
userSessionRule.logIn("john").addComponentUuidPermission(UserRole.USER, PROJECT_UUID, FILE_UUID);
- WsTester.TestRequest request = wsTester.newGetRequest("api/components", "app").setParam("uuid", FILE_UUID);
- request.execute().assertJson(getClass(), "app.json");
+ TestRequest request = wsTester.newRequest().setParam("uuid", FILE_UUID);
+ jsonAssert(request, "app.json");
}
@Test
@@ -102,8 +102,8 @@ public class AppActionTest {
userSessionRule
.logIn("john")
.addComponentUuidPermission(UserRole.USER, PROJECT_UUID, FILE_UUID);
- WsTester.TestRequest request = wsTester.newGetRequest("api/components", "app").setParam("uuid", FILE_UUID);
- request.execute().assertJson(getClass(), "app_with_measures.json");
+ TestRequest request = wsTester.newRequest().setParam("uuid", FILE_UUID);
+ jsonAssert(request, "app_with_measures.json");
}
@Test
@@ -113,8 +113,18 @@ public class AppActionTest {
dbTester.commit();
userSessionRule.logIn("john").addComponentUuidPermission(UserRole.USER, PROJECT_UUID, FILE_UUID);
- WsTester.TestRequest request = wsTester.newGetRequest("api/components", "app").setParam("uuid", FILE_UUID);
- request.execute().assertJson(getClass(), "app_with_ut_measure.json");
+ TestRequest request = wsTester.newRequest().setParam("uuid", FILE_UUID);
+ jsonAssert(request, "app_with_ut_measure.json");
+ }
+
+ @Test
+ public void define_app_action() {
+ WebService.Action action = wsTester.getDef();
+ assertThat(action).isNotNull();
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.handler()).isNotNull();
+ assertThat(action.params()).hasSize(3);
}
private void insertMetrics() {
@@ -153,4 +163,8 @@ public class AppActionTest {
.setData(data);
dbTester.getDbClient().measureDao().insert(dbTester.getSession(), measure);
}
+
+ private void jsonAssert(TestRequest request, String filename) {
+ JsonAssert.assertJson(request.execute().getInput()).isSimilarTo(getClass().getResource(getClass().getSimpleName()+"/"+ filename));
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
index 73db6132f2a..7fab77c7c15 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java
@@ -19,59 +19,39 @@
*/
package org.sonar.server.component.ws;
-import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.i18n.I18n;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.db.DbClient;
-import org.sonar.server.component.ComponentFinder;
-import org.sonar.server.component.index.ComponentIndex;
-import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.ws.WsTester;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.sonarqube.ws.client.component.ComponentsWsParameters.CONTROLLER_COMPONENTS;
public class ComponentsWsTest {
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
- private WebService.Controller controller;
+ private String actionKey = randomAlphanumeric(10);
+ private ComponentsWsAction action = new ComponentsWsAction() {
- @Before
- public void setUp() {
- Languages languages = mock(Languages.class, RETURNS_DEEP_STUBS);
- when(languages.all()).thenReturn(new Language[0]);
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ }
- WsTester tester = new WsTester(new ComponentsWs(
- new AppAction(mock(DbClient.class), userSessionRule, mock(ComponentFinder.class)),
- new SuggestionsAction(mock(DbClient.class), mock(ComponentIndex.class)),
- new SearchAction(mock(DbClient.class), mock(ResourceTypes.class), mock(I18n.class), userSessionRule, languages, TestDefaultOrganizationProvider.fromUuid("foo"))));
- controller = tester.controller("api/components");
- }
+ @Override
+ public void define(WebService.NewController context) {
+ context.createAction(actionKey).setHandler(this);
+ }
+ };
@Test
public void define_controller() {
+ WebService.Context context = new WebService.Context();
+ new ComponentsWs(action).define(context);
+ WebService.Controller controller = context.controller(CONTROLLER_COMPONENTS);
+
assertThat(controller).isNotNull();
assertThat(controller.description()).isNotEmpty();
assertThat(controller.since()).isEqualTo("4.2");
- assertThat(controller.actions()).hasSize(3);
- }
-
- @Test
- public void define_app_action() {
- WebService.Action action = controller.action("app");
- assertThat(action).isNotNull();
- assertThat(action.isInternal()).isTrue();
- assertThat(action.isPost()).isFalse();
- assertThat(action.handler()).isNotNull();
- assertThat(action.params()).hasSize(3);
+ assertThat(controller.actions()).extracting(WebService.Action::key).containsExactly(actionKey);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java
index 2b0233b0f62..71b3a73d7e5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java
@@ -39,6 +39,7 @@ import org.sonar.server.component.index.ComponentIndexDefinition;
import org.sonar.server.component.index.ComponentIndexer;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.favorite.FavoriteFinder;
import org.sonar.server.permission.index.AuthorizationTypeSupport;
import org.sonar.server.permission.index.PermissionIndexerTester;
import org.sonar.server.tester.UserSessionRule;
@@ -48,12 +49,15 @@ import org.sonarqube.ws.WsComponents.SuggestionsWsResponse;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Project;
import org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Suggestion;
+import static java.util.Collections.singletonList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
import static java.util.stream.IntStream.range;
import static java.util.stream.Stream.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.server.component.ws.SuggestionsAction.DEFAULT_LIMIT;
@@ -64,7 +68,6 @@ import static org.sonar.server.component.ws.SuggestionsAction.PARAM_QUERY;
import static org.sonar.server.component.ws.SuggestionsAction.PARAM_RECENTLY_BROWSED;
import static org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Category;
import static org.sonarqube.ws.WsComponents.SuggestionsWsResponse.Organization;
-import static org.sonarqube.ws.WsComponents.SuggestionsWsResponse.parseFrom;
public class SuggestionsActionTest {
@@ -76,8 +79,9 @@ public class SuggestionsActionTest {
public UserSessionRule userSessionRule = UserSessionRule.standalone();
private ComponentIndexer componentIndexer = new ComponentIndexer(db.getDbClient(), es.client());
+ private FavoriteFinder favoriteFinder = mock(FavoriteFinder.class);
private ComponentIndex index = new ComponentIndex(es.client(), new AuthorizationTypeSupport(userSessionRule));
- private SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index);
+ private SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index, favoriteFinder);
private OrganizationDto organization;
private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, componentIndexer);
private WsActionTester actionTester = new WsActionTester(underTest);
@@ -230,6 +234,29 @@ public class SuggestionsActionTest {
}
@Test
+ public void should_mark_favorite_items() throws Exception {
+ ComponentDto project = db.components().insertComponent(newProjectDto(organization));
+ ComponentDto favorite = newModuleDto(project).setName("Module1");
+ db.components().insertComponent(favorite);
+ doReturn(singletonList(favorite)).when(favoriteFinder).list();
+
+ ComponentDto nonFavorite = newModuleDto(project).setName("Module2");
+ db.components().insertComponent(nonFavorite);
+ componentIndexer.indexProject(project.projectUuid(), ProjectIndexer.Cause.PROJECT_CREATION);
+ authorizationIndexerTester.allowOnlyAnyone(project);
+
+ SuggestionsWsResponse response = actionTester.newRequest()
+ .setMethod("POST")
+ .setParam(PARAM_QUERY, "Module")
+ .executeProtobuf(SuggestionsWsResponse.class);
+
+ assertThat(response.getSuggestionsList())
+ .flatExtracting(Category::getSuggestionsList)
+ .extracting(Suggestion::getKey, Suggestion::getIsFavorite)
+ .containsExactly(tuple(favorite.getKey(), true), tuple(nonFavorite.getKey(), false));
+ }
+
+ @Test
public void should_propose_to_show_more_results_if_7_projects_are_found() throws Exception {
check_proposal_to_show_more_results(7, DEFAULT_LIMIT, 1L, null);
}