]> source.dussan.org Git - sonarqube.git/blob
da71abf575a5beffb05d28cb4a1ac2f225bc112e
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.server.component.ws;
21
22 import java.util.List;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25 import javax.annotation.Nullable;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.resources.Qualifiers;
30 import org.sonar.api.server.ws.Change;
31 import org.sonar.api.server.ws.WebService;
32 import org.sonar.api.utils.System2;
33 import org.sonar.core.util.stream.MoreCollectors;
34 import org.sonar.db.DbTester;
35 import org.sonar.db.component.ComponentDto;
36 import org.sonar.db.component.ComponentTesting;
37 import org.sonar.db.component.ResourceTypesRule;
38 import org.sonar.server.component.index.ComponentIndex;
39 import org.sonar.server.component.index.ComponentIndexer;
40 import org.sonar.server.es.EsTester;
41 import org.sonar.server.favorite.FavoriteFinder;
42 import org.sonar.server.permission.index.PermissionIndexerTester;
43 import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
44 import org.sonar.server.tester.UserSessionRule;
45 import org.sonar.server.ws.TestRequest;
46 import org.sonar.server.ws.TestResponse;
47 import org.sonar.server.ws.WsActionTester;
48 import org.sonarqube.ws.Components.SuggestionsWsResponse;
49 import org.sonarqube.ws.Components.SuggestionsWsResponse.Category;
50 import org.sonarqube.ws.Components.SuggestionsWsResponse.Suggestion;
51 import org.sonarqube.ws.MediaTypes;
52
53 import static java.util.Arrays.asList;
54 import static java.util.Collections.singletonList;
55 import static java.util.Optional.ofNullable;
56 import static java.util.stream.Collectors.joining;
57 import static java.util.stream.IntStream.range;
58 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
59 import static org.assertj.core.api.Assertions.assertThat;
60 import static org.assertj.core.groups.Tuple.tuple;
61 import static org.mockito.Mockito.doReturn;
62 import static org.mockito.Mockito.mock;
63 import static org.sonar.api.resources.Qualifiers.APP;
64 import static org.sonar.api.resources.Qualifiers.FILE;
65 import static org.sonar.api.resources.Qualifiers.MODULE;
66 import static org.sonar.api.resources.Qualifiers.PROJECT;
67 import static org.sonar.api.resources.Qualifiers.SUBVIEW;
68 import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
69 import static org.sonar.api.resources.Qualifiers.VIEW;
70 import static org.sonar.api.web.UserRole.USER;
71 import static org.sonar.db.component.ComponentTesting.newModuleDto;
72 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
73 import static org.sonar.db.component.ComponentTesting.newPublicProjectDto;
74 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_MORE;
75 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_QUERY;
76 import static org.sonar.server.component.ws.SuggestionsAction.PARAM_RECENTLY_BROWSED;
77 import static org.sonar.server.component.ws.SuggestionsAction.SHORT_INPUT_WARNING;
78 import static org.sonar.test.JsonAssert.assertJson;
79
80 public class SuggestionsActionTest {
81   private static final String[] SUGGESTION_QUALIFIERS = Stream.of(SuggestionCategory.values())
82     .map(SuggestionCategory::getQualifier)
83     .collect(MoreCollectors.toList()).toArray(new String[0]);
84
85   @Rule
86   public final DbTester db = DbTester.create(System2.INSTANCE);
87   @Rule
88   public final EsTester es = EsTester.create();
89   @Rule
90   public final UserSessionRule userSessionRule = UserSessionRule.standalone();
91   public final ResourceTypesRule resourceTypes = new ResourceTypesRule();
92
93   private final ComponentIndexer componentIndexer = new ComponentIndexer(db.getDbClient(), es.client());
94   private final FavoriteFinder favoriteFinder = mock(FavoriteFinder.class);
95   private final ComponentIndex index = new ComponentIndex(es.client(), new WebAuthorizationTypeSupport(userSessionRule), System2.INSTANCE);
96   private final SuggestionsAction underTest = new SuggestionsAction(db.getDbClient(), index, favoriteFinder, userSessionRule, resourceTypes);
97   private final PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, componentIndexer);
98   private final WsActionTester ws = new WsActionTester(underTest);
99
100   @Before
101   public void setUp() {
102     resourceTypes.setAllQualifiers(SUGGESTION_QUALIFIERS);
103   }
104
105   @Test
106   public void define_suggestions_action() {
107     WebService.Action action = ws.getDef();
108     assertThat(action).isNotNull();
109     assertThat(action.isInternal()).isTrue();
110     assertThat(action.isPost()).isFalse();
111     assertThat(action.handler()).isNotNull();
112     assertThat(action.responseExampleAsString()).isNotEmpty();
113     assertThat(action.params()).extracting(WebService.Param::key).containsExactlyInAnyOrder(
114       PARAM_MORE,
115       PARAM_QUERY,
116       PARAM_RECENTLY_BROWSED);
117     assertThat(action.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder(
118       tuple("8.4", "The use of 'DIR', 'FIL','UTS' as values for parameter 'more' is no longer supported"),
119       tuple("7.6", "The use of 'BRC' as value for parameter 'more' is deprecated"));
120
121     WebService.Param recentlyBrowsed = action.param(PARAM_RECENTLY_BROWSED);
122     assertThat(recentlyBrowsed.since()).isEqualTo("6.4");
123     assertThat(recentlyBrowsed.exampleValue()).isNotEmpty();
124     assertThat(recentlyBrowsed.description()).isNotEmpty();
125     assertThat(recentlyBrowsed.isRequired()).isFalse();
126
127     WebService.Param query = action.param(PARAM_QUERY);
128     assertThat(query.exampleValue()).isNotEmpty();
129     assertThat(query.description()).isNotEmpty();
130     assertThat(query.isRequired()).isFalse();
131   }
132
133   @Test
134   public void test_example_json_response() {
135     ComponentDto project1 = db.components().insertPublicProject(p -> p.setDbKey("org.sonarsource:sonarqube").setName("SonarSource :: SonarQube"));
136     ComponentDto project2 = db.components().insertPublicProject(p -> p.setDbKey("org.sonarsource:sonarlint").setName("SonarSource :: SonarLint"));
137     componentIndexer.indexAll();
138     authorizationIndexerTester.allowOnlyAnyone(project1);
139     authorizationIndexerTester.allowOnlyAnyone(project2);
140
141     TestResponse wsResponse = ws.newRequest()
142       .setParam(PARAM_QUERY, "Sonar")
143       .setParam(PARAM_RECENTLY_BROWSED, project1.getDbKey())
144       .setMethod("POST")
145       .setMediaType(MediaTypes.JSON)
146       .execute();
147
148     assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(wsResponse.getInput());
149   }
150
151   @Test
152   public void suggestions_without_query_should_contain_recently_browsed() {
153     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
154
155     componentIndexer.indexAll();
156     userSessionRule.addProjectPermission(USER, project);
157
158     SuggestionsWsResponse response = ws.newRequest()
159       .setMethod("POST")
160       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
161       .executeProtobuf(SuggestionsWsResponse.class);
162
163     // assert match in qualifier "TRK"
164     assertThat(response.getResultsList())
165       .filteredOn(q -> q.getItemsCount() > 0)
166       .extracting(Category::getQ)
167       .containsExactly(PROJECT);
168
169     // assert correct id to be found
170     assertThat(response.getResultsList())
171       .flatExtracting(Category::getItemsList)
172       .extracting(Suggestion::getKey, Suggestion::getIsRecentlyBrowsed)
173       .containsExactly(tuple(project.getDbKey(), true));
174   }
175
176   @Test
177   public void suggestions_without_query_should_contain_recently_browsed_public_project() {
178     ComponentDto project = db.components().insertComponent(newPublicProjectDto());
179
180     componentIndexer.indexAll();
181
182     SuggestionsWsResponse response = ws.newRequest()
183       .setMethod("POST")
184       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
185       .executeProtobuf(SuggestionsWsResponse.class);
186
187     // assert match in qualifier "TRK"
188     assertThat(response.getResultsList())
189       .filteredOn(q -> q.getItemsCount() > 0)
190       .extracting(Category::getQ)
191       .containsExactly(PROJECT);
192
193     // assert correct id to be found
194     assertThat(response.getResultsList())
195       .flatExtracting(Category::getItemsList)
196       .extracting(Suggestion::getKey, Suggestion::getIsRecentlyBrowsed)
197       .containsExactly(tuple(project.getDbKey(), true));
198   }
199
200   @Test
201   public void suggestions_without_query_should_not_contain_recently_browsed_without_permission() {
202     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
203
204     componentIndexer.indexAll();
205
206     SuggestionsWsResponse response = ws.newRequest()
207       .setMethod("POST")
208       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
209       .executeProtobuf(SuggestionsWsResponse.class);
210
211     assertThat(response.getResultsList())
212       .flatExtracting(Category::getItemsList)
213       .isEmpty();
214   }
215
216   @Test
217   public void suggestions_without_query_should_contain_favorites() {
218     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
219     doReturn(singletonList(project)).when(favoriteFinder).list();
220
221     componentIndexer.indexAll();
222     userSessionRule.addProjectPermission(USER, project);
223
224     SuggestionsWsResponse response = ws.newRequest()
225       .setMethod("POST")
226       .executeProtobuf(SuggestionsWsResponse.class);
227
228     // assert match in qualifier "TRK"
229     assertThat(response.getResultsList())
230       .filteredOn(q -> q.getItemsCount() > 0)
231       .extracting(Category::getQ)
232       .containsExactly(PROJECT);
233
234     // assert correct id to be found
235     assertThat(response.getResultsList())
236       .flatExtracting(Category::getItemsList)
237       .extracting(Suggestion::getKey, Suggestion::getIsFavorite)
238       .containsExactly(tuple(project.getDbKey(), true));
239   }
240
241   @Test
242   public void suggestions_without_query_should_not_contain_favorites_without_permission() {
243     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
244     doReturn(singletonList(project)).when(favoriteFinder).list();
245
246     componentIndexer.indexAll();
247
248     SuggestionsWsResponse response = ws.newRequest()
249       .setMethod("POST")
250       .executeProtobuf(SuggestionsWsResponse.class);
251
252     assertThat(response.getResultsList())
253       .flatExtracting(Category::getItemsList)
254       .isEmpty();
255   }
256
257   @Test
258   public void suggestions_without_query_should_contain_recently_browsed_favorites() {
259     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
260     doReturn(singletonList(project)).when(favoriteFinder).list();
261
262     componentIndexer.indexAll();
263     userSessionRule.addProjectPermission(USER, project);
264
265     SuggestionsWsResponse response = ws.newRequest()
266       .setMethod("POST")
267       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
268       .executeProtobuf(SuggestionsWsResponse.class);
269
270     // assert match in qualifier "TRK"
271     assertThat(response.getResultsList())
272       .filteredOn(q -> q.getItemsCount() > 0)
273       .extracting(Category::getQ)
274       .containsExactly(Qualifiers.PROJECT);
275
276     // assert correct id to be found
277     assertThat(response.getResultsList())
278       .flatExtracting(Category::getItemsList)
279       .extracting(Suggestion::getKey, Suggestion::getIsFavorite, Suggestion::getIsRecentlyBrowsed)
280       .containsExactly(tuple(project.getDbKey(), true, true));
281   }
282
283   @Test
284   public void suggestions_without_query_should_not_contain_matches_that_are_neither_favorites_nor_recently_browsed() {
285     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
286
287     componentIndexer.indexAll();
288     userSessionRule.addProjectPermission(USER, project);
289
290     SuggestionsWsResponse response = ws.newRequest()
291       .setMethod("POST")
292       .executeProtobuf(SuggestionsWsResponse.class);
293
294     // assert match in qualifier "TRK"
295     assertThat(response.getResultsList())
296       .filteredOn(q -> q.getItemsCount() > 0)
297       .extracting(Category::getQ)
298       .isEmpty();
299   }
300
301   @Test
302   public void suggestions_without_query_should_order_results() {
303     ComponentDto project1 = db.components().insertComponent(newPrivateProjectDto().setName("Alpha"));
304     ComponentDto project2 = db.components().insertComponent(newPrivateProjectDto().setName("Bravo"));
305     ComponentDto project3 = db.components().insertComponent(newPrivateProjectDto().setName("Charlie"));
306     ComponentDto project4 = db.components().insertComponent(newPrivateProjectDto().setName("Delta"));
307     doReturn(asList(project4, project2)).when(favoriteFinder).list();
308
309     componentIndexer.indexAll();
310     userSessionRule.addProjectPermission(USER, project1);
311     userSessionRule.addProjectPermission(USER, project2);
312     userSessionRule.addProjectPermission(USER, project3);
313     userSessionRule.addProjectPermission(USER, project4);
314
315     SuggestionsWsResponse response = ws.newRequest()
316       .setMethod("POST")
317       .setParam(PARAM_RECENTLY_BROWSED, Stream.of(project3, project1).map(ComponentDto::getDbKey).collect(joining(",")))
318       .executeProtobuf(SuggestionsWsResponse.class);
319
320     // assert order of keys
321     assertThat(response.getResultsList())
322       .flatExtracting(Category::getItemsList)
323       .extracting(Suggestion::getName, Suggestion::getIsFavorite, Suggestion::getIsRecentlyBrowsed)
324       .containsExactly(
325         tuple("Bravo", true, false),
326         tuple("Delta", true, false),
327         tuple("Alpha", false, true),
328         tuple("Charlie", false, true));
329   }
330
331   @Test
332   public void suggestions_without_query_should_return_empty_qualifiers() {
333     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
334     componentIndexer.indexOnAnalysis(project.projectUuid());
335     userSessionRule.addProjectPermission(USER, project);
336
337     SuggestionsWsResponse response = ws.newRequest()
338       .setMethod("POST")
339       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
340       .executeProtobuf(SuggestionsWsResponse.class);
341
342     assertThat(response.getResultsList())
343       .extracting(Category::getQ, Category::getItemsCount)
344       .containsExactlyInAnyOrder(tuple("VW", 0), tuple("APP", 0), tuple("SVW", 0), tuple("TRK", 1))
345       .doesNotContain(tuple("BRC", 0), tuple("FIL", 0), tuple("UTS", 0));
346   }
347
348   @Test
349   public void suggestions_should_filter_allowed_qualifiers() {
350     resourceTypes.setAllQualifiers(PROJECT, MODULE, FILE, UNIT_TEST_FILE);
351     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
352     componentIndexer.indexOnAnalysis(project.projectUuid());
353     userSessionRule.addProjectPermission(USER, project);
354
355     SuggestionsWsResponse response = ws.newRequest()
356       .setMethod("POST")
357       .setParam(PARAM_RECENTLY_BROWSED, project.getDbKey())
358       .executeProtobuf(SuggestionsWsResponse.class);
359
360     assertThat(response.getResultsList())
361       .extracting(Category::getQ)
362       .containsExactlyInAnyOrder(PROJECT).doesNotContain(MODULE, FILE, UNIT_TEST_FILE);
363   }
364
365   @Test
366   public void exact_match_in_one_qualifier() {
367     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
368
369     componentIndexer.indexAll();
370     authorizationIndexerTester.allowOnlyAnyone(project);
371
372     SuggestionsWsResponse response = ws.newRequest()
373       .setMethod("POST")
374       .setParam(PARAM_QUERY, project.getDbKey())
375       .executeProtobuf(SuggestionsWsResponse.class);
376
377     // assert match in qualifier "TRK"
378     assertThat(response.getResultsList())
379       .filteredOn(q -> q.getItemsCount() > 0)
380       .extracting(Category::getQ)
381       .containsExactly(PROJECT);
382
383     // assert correct id to be found
384     assertThat(response.getResultsList())
385       .flatExtracting(Category::getItemsList)
386       .extracting(Suggestion::getKey)
387       .containsExactly(project.getDbKey());
388   }
389
390   @Test
391   public void should_not_return_suggestion_on_non_existing_project() {
392     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
393
394     componentIndexer.indexAll();
395     authorizationIndexerTester.allowOnlyAnyone(project);
396
397     db.getDbClient().componentDao().delete(db.getSession(), project.uuid());
398     db.commit();
399
400     SuggestionsWsResponse response = ws.newRequest()
401       .setMethod("POST")
402       .setParam(PARAM_QUERY, project.getDbKey())
403       .executeProtobuf(SuggestionsWsResponse.class);
404
405     // assert match in qualifier "TRK"
406     assertThat(response.getResultsList())
407       .filteredOn(q -> q.getItemsCount() > 0)
408       .isEmpty();
409   }
410
411   @Test
412   public void must_not_search_if_no_valid_tokens_are_provided() {
413     ComponentDto project = db.components().insertComponent(newPrivateProjectDto().setName("SonarQube"));
414
415     componentIndexer.indexAll();
416     authorizationIndexerTester.allowOnlyAnyone(project);
417
418     SuggestionsWsResponse response = ws.newRequest()
419       .setMethod("POST")
420       .setParam(PARAM_QUERY, "S o")
421       .executeProtobuf(SuggestionsWsResponse.class);
422
423     assertThat(response.getResultsList()).filteredOn(q -> q.getItemsCount() > 0).isEmpty();
424     assertThat(response.getWarning()).contains(SHORT_INPUT_WARNING);
425   }
426
427   @Test
428   public void should_warn_about_short_inputs() {
429     SuggestionsWsResponse response = ws.newRequest()
430       .setMethod("POST")
431       .setParam(PARAM_QUERY, "validLongToken x")
432       .executeProtobuf(SuggestionsWsResponse.class);
433
434     assertThat(response.getWarning()).contains(SHORT_INPUT_WARNING);
435   }
436
437   @Test
438   public void should_warn_about_short_inputs_but_return_results_based_on_other_terms() {
439     ComponentDto project = db.components().insertComponent(newPrivateProjectDto().setName("SonarQube"));
440
441     componentIndexer.indexAll();
442     authorizationIndexerTester.allowOnlyAnyone(project);
443
444     SuggestionsWsResponse response = ws.newRequest()
445       .setMethod("POST")
446       .setParam(PARAM_QUERY, "Sonar Q")
447       .executeProtobuf(SuggestionsWsResponse.class);
448
449     assertThat(response.getResultsList())
450       .flatExtracting(Category::getItemsList)
451       .extracting(Suggestion::getKey)
452       .contains(project.getDbKey());
453     assertThat(response.getWarning()).contains(SHORT_INPUT_WARNING);
454   }
455
456   @Test
457   public void should_contain_component_names() {
458     ComponentDto project1 = db.components().insertComponent(newPrivateProjectDto().setName("Project1"));
459     componentIndexer.indexOnAnalysis(project1.projectUuid());
460     authorizationIndexerTester.allowOnlyAnyone(project1);
461
462     SuggestionsWsResponse response = ws.newRequest()
463       .setMethod("POST")
464       .setParam(PARAM_QUERY, "Project")
465       .executeProtobuf(SuggestionsWsResponse.class);
466
467     assertThat(response.getResultsList())
468       .flatExtracting(Category::getItemsList)
469       .extracting(Suggestion::getKey, Suggestion::getName)
470       .containsExactlyInAnyOrder(tuple(project1.getDbKey(), project1.name()));
471   }
472
473   @Test
474   public void should_not_return_modules() {
475     ComponentDto project = db.components().insertComponent(newPrivateProjectDto().setName("ProjectWithModules"));
476     db.components().insertComponent(newModuleDto(project).setName("Module1"));
477     db.components().insertComponent(newModuleDto(project).setName("Module2"));
478     componentIndexer.indexOnAnalysis(project.projectUuid());
479     authorizationIndexerTester.allowOnlyAnyone(project);
480
481     SuggestionsWsResponse response = ws.newRequest()
482       .setMethod("POST")
483       .setParam(PARAM_QUERY, "Module")
484       .executeProtobuf(SuggestionsWsResponse.class);
485
486     assertThat(response.getResultsList())
487       .flatExtracting(Category::getItemsList)
488       .extracting(Suggestion::getKey)
489       .containsOnly(project.getDbKey());
490   }
491
492   @Test
493   public void should_mark_recently_browsed_items() {
494     ComponentDto project = db.components().insertComponent(newPrivateProjectDto().setName("ProjectModule"));
495     ComponentDto module1 = newModuleDto(project).setName("Module1");
496     db.components().insertComponent(module1);
497     ComponentDto module2 = newModuleDto(project).setName("Module2");
498     db.components().insertComponent(module2);
499     componentIndexer.indexOnAnalysis(project.projectUuid());
500     authorizationIndexerTester.allowOnlyAnyone(project);
501
502     SuggestionsWsResponse response = ws.newRequest()
503       .setMethod("POST")
504       .setParam(PARAM_QUERY, "Module")
505       .setParam(PARAM_RECENTLY_BROWSED, Stream.of(module1.getDbKey(), project.getDbKey()).collect(joining(",")))
506       .executeProtobuf(SuggestionsWsResponse.class);
507
508     assertThat(response.getResultsList())
509       .flatExtracting(Category::getItemsList)
510       .extracting(Suggestion::getIsRecentlyBrowsed)
511       .containsExactly(true);
512   }
513
514   @Test
515   public void should_mark_favorite_items() {
516     ComponentDto favouriteProject = db.components().insertComponent(newPrivateProjectDto().setName("Project1"));
517     ComponentDto nonFavouriteProject = db.components().insertComponent(newPublicProjectDto().setName("Project2"));
518
519     doReturn(singletonList(favouriteProject)).when(favoriteFinder).list();
520     componentIndexer.indexOnAnalysis(favouriteProject.projectUuid());
521     componentIndexer.indexOnAnalysis(nonFavouriteProject.projectUuid());
522     authorizationIndexerTester.allowOnlyAnyone(favouriteProject, nonFavouriteProject);
523
524     SuggestionsWsResponse response = ws.newRequest()
525       .setMethod("POST")
526       .setParam(PARAM_QUERY, "Project")
527       .executeProtobuf(SuggestionsWsResponse.class);
528
529     assertThat(response.getResultsList())
530       .flatExtracting(Category::getItemsList)
531       .extracting(Suggestion::getKey, Suggestion::getIsFavorite)
532       .containsExactly(tuple(favouriteProject.getDbKey(), true), tuple(nonFavouriteProject.getDbKey(), false));
533   }
534
535   @Test
536   public void should_return_empty_qualifiers() {
537     ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
538     componentIndexer.indexOnAnalysis(project.projectUuid());
539     authorizationIndexerTester.allowOnlyAnyone(project);
540
541     SuggestionsWsResponse response = ws.newRequest()
542       .setMethod("POST")
543       .setParam(PARAM_QUERY, project.name())
544       .executeProtobuf(SuggestionsWsResponse.class);
545
546     assertThat(response.getResultsList())
547       .extracting(Category::getQ, Category::getItemsCount)
548       .containsExactlyInAnyOrder(tuple("VW", 0), tuple("SVW", 0), tuple("APP", 0), tuple("TRK", 1));
549   }
550
551   @Test
552   public void should_only_provide_project_for_certain_qualifiers() {
553     String query = randomAlphabetic(10);
554
555     ComponentDto app = db.components().insertPublicApplication(v -> v.setName(query));
556     ComponentDto view = db.components().insertPublicPortfolio(v -> v.setName(query));
557     ComponentDto subView = db.components().insertComponent(ComponentTesting.newSubView(view).setName(query));
558     ComponentDto project = db.components().insertPrivateProject(p -> p.setName(query));
559     ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project).setName(query));
560     ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module).setName(query));
561     ComponentDto test = db.components().insertComponent(ComponentTesting.newFileDto(module).setName(query).setQualifier(UNIT_TEST_FILE));
562     componentIndexer.indexAll();
563     authorizationIndexerTester.allowOnlyAnyone(project);
564     authorizationIndexerTester.allowOnlyAnyone(view);
565     authorizationIndexerTester.allowOnlyAnyone(app);
566
567     SuggestionsWsResponse response = ws.newRequest()
568       .setMethod("POST")
569       .setParam(PARAM_QUERY, project.name())
570       .executeProtobuf(SuggestionsWsResponse.class);
571
572     assertThat(response.getResultsList())
573       .extracting(Category::getQ, c -> c.getItemsList().stream().map(Suggestion::hasProject).findFirst().orElse(null))
574       .containsExactlyInAnyOrder(
575         tuple(SuggestionCategory.APP.getName(), false),
576         tuple(SuggestionCategory.VIEW.getName(), false),
577         tuple(SuggestionCategory.SUBVIEW.getName(), false),
578         tuple(SuggestionCategory.PROJECT.getName(), false));
579   }
580
581   @Test
582   public void does_not_return_branches() {
583     ComponentDto project = db.components().insertPublicProject();
584     authorizationIndexerTester.allowOnlyAnyone(project);
585     ComponentDto branch = db.components().insertProjectBranch(project);
586     componentIndexer.indexAll();
587     authorizationIndexerTester.allowOnlyAnyone(project);
588
589     SuggestionsWsResponse response = ws.newRequest()
590       .setMethod("POST")
591       .setParam(PARAM_QUERY, project.name())
592       .executeProtobuf(SuggestionsWsResponse.class);
593
594     assertThat(response.getResultsList())
595       .filteredOn(c -> "TRK".equals(c.getQ()))
596       .extracting(Category::getItemsList)
597       .hasSize(1);
598   }
599
600   @Test
601   public void should_not_propose_to_show_more_results_if_0_projects_are_found() {
602     check_proposal_to_show_more_results(0, 0, 0L, null, true);
603   }
604
605   @Test
606   public void should_not_propose_to_show_more_results_if_0_projects_are_found_and_no_search_query_is_provided() {
607     check_proposal_to_show_more_results(0, 0, 0L, null, false);
608   }
609
610   @Test
611   public void should_not_propose_to_show_more_results_if_5_projects_are_found() {
612     check_proposal_to_show_more_results(5, 5, 0L, null, true);
613   }
614
615   @Test
616   public void should_not_propose_to_show_more_results_if_5_projects_are_found_and_no_search_query_is_provided() {
617     check_proposal_to_show_more_results(5, 5, 0L, null, false);
618   }
619
620   @Test
621   public void should_not_propose_to_show_more_results_if_6_projects_are_found() {
622     check_proposal_to_show_more_results(6, 6, 0L, null, true);
623   }
624
625   @Test
626   public void should_not_propose_to_show_more_results_if_6_projects_are_found_and_no_search_query_is_provided() {
627     check_proposal_to_show_more_results(6, 6, 0L, null, false);
628   }
629
630   @Test
631   public void should_propose_to_show_more_results_if_7_projects_are_found() {
632     check_proposal_to_show_more_results(7, 6, 1L, null, true);
633   }
634
635   @Test
636   public void should_propose_to_show_more_results_if_7_projects_are_found_and_no_search_query_is_provided() {
637     check_proposal_to_show_more_results(7, 6, 1L, null, false);
638   }
639
640   @Test
641   public void show_more_results_if_requested_and_5_projects_are_found() {
642     check_proposal_to_show_more_results(5, 0, 0L, SuggestionCategory.PROJECT, true);
643   }
644
645   @Test
646   public void show_more_results_if_requested_and_5_projects_are_found_and_no_search_query_is_provided() {
647     check_proposal_to_show_more_results(5, 0, 0L, SuggestionCategory.PROJECT, false);
648   }
649
650   @Test
651   public void show_more_results_if_requested_and_6_projects_are_found() {
652     check_proposal_to_show_more_results(6, 0, 0L, SuggestionCategory.PROJECT, true);
653   }
654
655   @Test
656   public void show_more_results_if_requested_and_6_projects_are_found_and_no_search_query_is_provided() {
657     check_proposal_to_show_more_results(6, 0, 0L, SuggestionCategory.PROJECT, false);
658   }
659
660   @Test
661   public void show_more_results_if_requested_and_7_projects_are_found() {
662     check_proposal_to_show_more_results(7, 1, 0L, SuggestionCategory.PROJECT, true);
663   }
664
665   @Test
666   public void show_more_results_if_requested_and_7_projects_are_found_and_no_search_query_is_provided() {
667     check_proposal_to_show_more_results(7, 1, 0L, SuggestionCategory.PROJECT, false);
668   }
669
670   @Test
671   public void show_more_results_if_requested_and_26_projects_are_found() {
672     check_proposal_to_show_more_results(26, 20, 0L, SuggestionCategory.PROJECT, true);
673   }
674
675   @Test
676   public void show_more_results_if_requested_and_26_projects_are_found_and_no_search_query_is_provided() {
677     check_proposal_to_show_more_results(26, 20, 0L, SuggestionCategory.PROJECT, false);
678   }
679
680   @Test
681   public void show_more_results_if_requested_and_27_projects_are_found() {
682     check_proposal_to_show_more_results(27, 20, 1L, SuggestionCategory.PROJECT, true);
683   }
684
685   @Test
686   public void show_more_results_if_requested_and_27_projects_are_found_and_no_search_query_is_provided() {
687     check_proposal_to_show_more_results(27, 20, 1L, SuggestionCategory.PROJECT, false);
688   }
689
690   @Test
691   public void show_more_results_filter_out_if_non_allowed_qualifiers() {
692     resourceTypes.setAllQualifiers(APP, VIEW, SUBVIEW);
693
694     check_proposal_to_show_more_results(10, 0, 0L, SuggestionCategory.PROJECT, true);
695   }
696
697   private void check_proposal_to_show_more_results(int numberOfProjects, int expectedNumberOfResults, long expectedNumberOfMoreResults, @Nullable SuggestionCategory more,
698     boolean useQuery) {
699     String namePrefix = "MyProject";
700
701     List<ComponentDto> projects = range(0, numberOfProjects)
702       .mapToObj(i -> db.components().insertComponent(newPublicProjectDto().setName(namePrefix + i)))
703       .collect(Collectors.toList());
704
705     componentIndexer.indexAll();
706     projects.forEach(authorizationIndexerTester::allowOnlyAnyone);
707
708     TestRequest request = ws.newRequest()
709       .setMethod("POST");
710     if (useQuery) {
711       request.setParam(PARAM_QUERY, namePrefix);
712     } else {
713       doReturn(projects).when(favoriteFinder).list();
714     }
715     ofNullable(more).ifPresent(c -> request.setParam(PARAM_MORE, c.getName()));
716     SuggestionsWsResponse response = request
717       .executeProtobuf(SuggestionsWsResponse.class);
718
719     // include limited number of results in the response
720     assertThat(response.getResultsList())
721       .flatExtracting(Category::getItemsList)
722       .hasSize(expectedNumberOfResults);
723
724     // indicate, that there are more results
725     if (expectedNumberOfResults == 0 && expectedNumberOfMoreResults == 0) {
726       assertThat(response.getResultsList())
727         .filteredOn(q -> q.getItemsCount() > 0)
728         .isEmpty();
729     } else {
730       assertThat(response.getResultsList())
731         .filteredOn(c -> "TRK".equals(c.getQ()))
732         .extracting(Category::getMore)
733         .containsExactly(expectedNumberOfMoreResults);
734       response.getResultsList().stream()
735         .filter(c -> !"TRK".equals(c.getQ()))
736         .map(Category::getMore)
737         .forEach(m -> assertThat(m).isEqualTo(0L));
738     }
739   }
740 }