Browse Source

SONAR-7286 Create WS api/favorites/search

tags/6.3-RC1
Teryk Bellahsene 7 years ago
parent
commit
785a310641
20 changed files with 571 additions and 220 deletions
  1. 8
    8
      it/it-tests/src/test/java/it/analysis/FavoriteTest.java
  2. 7
    11
      it/it-tests/src/test/java/it/user/FavoritesWsTest.java
  3. 0
    1
      server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java
  4. 67
    0
      server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java
  5. 4
    1
      server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java
  6. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
  7. 191
    0
      server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java
  8. 0
    48
      server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java
  9. 0
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java
  10. 24
    0
      server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json
  11. 0
    18
      server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml
  12. 0
    1
      server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java
  13. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java
  14. 210
    0
      server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java
  15. 0
    39
      server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java
  16. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java
  17. 0
    89
      server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb
  18. 17
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java
  19. 1
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java
  20. 39
    0
      sonar-ws/src/main/protobuf/ws-favorites.proto

+ 8
- 8
it/it-tests/src/test/java/it/analysis/FavoriteTest.java View File

@@ -28,9 +28,9 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Favorites;
import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.WsPermissions;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsClient;
import org.sonarqube.ws.client.permission.AddProjectCreatorToTemplateWsRequest;
import org.sonarqube.ws.client.permission.RemoveProjectCreatorFromTemplateWsRequest;
@@ -71,8 +71,8 @@ public class FavoriteTest {

orchestrator.executeBuild(sampleProject);

String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
assertThat(response).contains(PROJECT_KEY);
Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
assertThat(response.getFavoritesList()).extracting(Favorite::getKey).contains(PROJECT_KEY);
}

@Test
@@ -81,8 +81,8 @@ public class FavoriteTest {

orchestrator.executeBuild(sampleProject);

String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
assertThat(response).doesNotContain(PROJECT_KEY);
Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY);
}

@Test
@@ -94,8 +94,8 @@ public class FavoriteTest {

orchestrator.executeBuild(sampleProject);

String response = adminWsClient.wsConnector().call(new GetRequest("api/favourites").setMediaType(MediaTypes.JSON)).content();
assertThat(response).doesNotContain(PROJECT_KEY);
Favorites.SearchResponse response = adminWsClient.favorites().search(null, null);
assertThat(response.getFavoritesList()).extracting(Favorite::getKey).doesNotContain(PROJECT_KEY);
}

private static SonarScanner createScannerWithUserCredentials() {

+ 7
- 11
it/it-tests/src/test/java/it/user/FavoritesWsTest.java View File

@@ -26,9 +26,7 @@ import java.util.List;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.sonar.wsclient.Sonar;
import org.sonar.wsclient.services.Favourite;
import org.sonar.wsclient.services.FavouriteQuery;
import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.client.WsClient;

import static org.assertj.core.api.Assertions.assertThat;
@@ -53,24 +51,22 @@ public class FavoritesWsTest {

@Test
public void favorites_web_service() {
Sonar oldWsClient = orchestrator.getServer().getAdminWsClient();

// GET (nothing)
List<Favourite> favourites = oldWsClient.findAll(new FavouriteQuery());
assertThat(favourites).isEmpty();
List<Favorite> favorites = adminClient.favorites().search(null, null).getFavoritesList();
assertThat(favorites).isEmpty();

// POST (create favorites)
adminClient.favorites().add("sample");
adminClient.favorites().add("sample:src/main/xoo/sample/Sample.xoo");

// GET (created favorites)
favourites = oldWsClient.findAll(new FavouriteQuery());
assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo");
favorites = adminClient.favorites().search(null, null).getFavoritesList();
assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo");

// DELETE (a favorite)
adminClient.favorites().remove("sample");
favourites = oldWsClient.findAll(new FavouriteQuery());
assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo");
favorites = adminClient.favorites().search(null, null).getFavoritesList();
assertThat(favorites.stream().map(Favorite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo");
}

}

+ 0
- 1
server/sonar-server/src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java View File

@@ -44,7 +44,6 @@ public class JwtCsrfVerifier {
private static final String API_URL = "/api";
private static final Set<String> RAILS_UPDATE_API_URLS = ImmutableSet.of(
"/api/events",
"/api/favourites",
"/api/issues/add_comment",
"/api/issues/delete_comment",
"/api/issues/edit_comment",

+ 67
- 0
server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteFinder.java View File

@@ -0,0 +1,67 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
import org.sonar.server.user.UserSession;

import static java.util.Collections.emptyList;
import static org.sonar.core.util.stream.Collectors.toList;
import static org.sonar.server.favorite.FavoriteUpdater.PROP_FAVORITE_KEY;

public class FavoriteFinder {
private final DbClient dbClient;
private final UserSession userSession;

public FavoriteFinder(DbClient dbClient, UserSession userSession) {
this.dbClient = dbClient;
this.userSession = userSession;
}

/**
* @return the list of favorite components of the authenticated user. Empty list if the user is not authenticated
*/
public List<ComponentDto> list() {
if (!userSession.isLoggedIn()) {
return emptyList();
}

try (DbSession dbSession = dbClient.openSession(false)) {
PropertyQuery dbQuery = PropertyQuery.builder()
.setKey(PROP_FAVORITE_KEY)
.setUserId(userSession.getUserId())
.build();
Set<Long> componentIds = dbClient.propertiesDao().selectByQuery(dbQuery, dbSession).stream().map(PropertyDto::getResourceId).collect(Collectors.toSet());

return dbClient.componentDao().selectByIds(dbSession, componentIds).stream()
.sorted(Comparator.comparing(ComponentDto::name))
.collect(toList());
}
}
}

+ 4
- 1
server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java View File

@@ -24,16 +24,19 @@ import org.sonar.core.platform.Module;
import org.sonar.server.favorite.ws.AddAction;
import org.sonar.server.favorite.ws.FavoritesWs;
import org.sonar.server.favorite.ws.RemoveAction;
import org.sonar.server.favorite.ws.SearchAction;

public class FavoriteModule extends Module {

@Override
protected void configureModule() {
add(
FavoriteFinder.class,
FavoriteUpdater.class,
FavoritesWs.class,
AddAction.class,
RemoveAction.class);
RemoveAction.class,
SearchAction.class);
}

}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java View File

@@ -30,7 +30,7 @@ import org.sonar.server.user.UserSession;
import static org.sonar.server.ws.WsUtils.checkRequest;

public class FavoriteUpdater {
private static final String PROP_FAVORITE_KEY = "favourite";
static final String PROP_FAVORITE_KEY = "favourite";

private final DbClient dbClient;
private final UserSession userSession;

+ 191
- 0
server/sonar-server/src/main/java/org/sonar/server/favorite/ws/SearchAction.java View File

@@ -0,0 +1,191 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.ws;

import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.Paging;
import org.sonar.api.web.UserRole;
import org.sonar.core.util.stream.Collectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.favorite.FavoriteFinder;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.Favorites.SearchResponse;

import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.core.util.stream.Collectors.toOneElement;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_SEARCH;

public class SearchAction implements FavoritesWsAction {
private final FavoriteFinder favoriteFinder;
private final DbClient dbClient;
private final UserSession userSession;

public SearchAction(FavoriteFinder favoriteFinder, DbClient dbClient, UserSession userSession) {
this.favoriteFinder = favoriteFinder;
this.dbClient = dbClient;
this.userSession = userSession;
}

@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_SEARCH)
.setDescription("Search for the authenticated user favorites.<br>" +
"Requires authentication.")
.setSince("6.3")
.setResponseExample(getClass().getResource("search-example.json"))
.setHandler(this);

action.addPagingParams(100, 500);
}

@Override
public void handle(Request request, Response response) throws Exception {
SearchResponse wsResponse = Stream.of(request)
.map(search())
.map(new ResponseBuilder())
.collect(Collectors.toOneElement());
writeProtobuf(wsResponse, request, response);
}

private Function<Request, SearchResults> search() {
return request -> {
try (DbSession dbSession = dbClient.openSession(false)) {
return Stream.of(request)
.peek(checkAuthentication(userSession))
.map(SearchResults.builder(dbSession))
.peek(addAuthorizedProjectUuids())
.peek(addFavorites())
.map(SearchResults.Builder::build)
.collect(Collectors.toOneElement());
}
};
}

private Consumer<SearchResults.Builder> addFavorites() {
return results -> results.allFavorites = favoriteFinder.list();
}

private Consumer<SearchResults.Builder> addAuthorizedProjectUuids() {
return results -> results.authorizedProjectUuids = ImmutableSet
.copyOf(dbClient.authorizationDao().selectAuthorizedRootProjectsUuids(results.dbSession, userSession.getUserId(), UserRole.USER));
}

private static Consumer<Request> checkAuthentication(UserSession userSession) {
return r -> userSession.checkLoggedIn();
}

private static class SearchResults {
private final List<ComponentDto> favorites;
private final Paging paging;

private SearchResults(Builder builder) {
Predicate<ComponentDto> authorizedProjects = c -> builder.authorizedProjectUuids.contains(c.projectUuid());
int total = (int) builder.allFavorites.stream().filter(authorizedProjects).count();
this.paging = Paging.forPageIndex(builder.page).withPageSize(builder.pageSize).andTotal(total);
this.favorites = builder.allFavorites.stream()
.filter(authorizedProjects)
.skip(paging.offset())
.limit(paging.pageSize())
.collect(Collectors.toList());
}

static Function<Request, Builder> builder(DbSession dbSession) {
return request -> new Builder(dbSession, request);
}

private static class Builder {
private final DbSession dbSession;
private final int page;
private final int pageSize;
private Set<String> authorizedProjectUuids;
private List<ComponentDto> allFavorites;

private Builder(DbSession dbSession, Request request) {
this.dbSession = dbSession;
this.page = request.mandatoryParamAsInt(Param.PAGE);
this.pageSize = request.mandatoryParamAsInt(Param.PAGE_SIZE);
}

public SearchResults build() {
return new SearchResults(this);
}
}
}

private static class ResponseBuilder implements Function<SearchResults, SearchResponse> {
private final SearchResponse.Builder response;
private final Favorite.Builder favorite;

private ResponseBuilder() {
this.response = SearchResponse.newBuilder();
this.favorite = Favorite.newBuilder();
}

@Override
public SearchResponse apply(SearchResults searchResults) {
return Stream.of(searchResults)
.peek(addPaging())
.peek(addFavorites())
.map(results -> response.build())
.collect(toOneElement());
}

private Consumer<SearchResults> addPaging() {
return results -> response.setPaging(Common.Paging.newBuilder()
.setPageIndex(results.paging.pageIndex())
.setPageSize(results.paging.pageSize())
.setTotal(results.paging.total()));
}

private Consumer<SearchResults> addFavorites() {
return results -> results.favorites.stream()
.map(toWsFavorite())
.forEach(response::addFavorites);
}

private Function<ComponentDto, Favorite> toWsFavorite() {
return componentDto -> {
favorite
.clear()
.setKey(componentDto.key());
setNullable(componentDto.name(), favorite::setName);
setNullable(componentDto.qualifier(), favorite::setQualifier);
return favorite.build();
};
}

}
}

+ 0
- 48
server/sonar-server/src/main/java/org/sonar/server/user/ws/FavouritesWs.java View File

@@ -1,48 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.ws;

import org.sonar.api.server.ws.RailsHandler;
import org.sonar.api.server.ws.WebService;

import static org.sonar.api.server.ws.RailsHandler.addFormatParam;

public class FavouritesWs implements WebService {

@Override
public void define(Context context) {
NewController controller = context.createController("api/favourites");
controller.setDescription("Manage user favorites.");
controller.setSince("2.6");

defineIndexAction(controller);

controller.done();
}

private void defineIndexAction(NewController controller) {
NewAction action = controller.createAction("index")
.setDescription("Documentation of this web service is available <a href=\"http://redirect.sonarsource.com/doc/old-web-service-api.html\">here</a>")
.setResponseExample(getClass().getResource("favourites-index-example.xml"))
.setSince("2.6")
.setHandler(RailsHandler.INSTANCE);
addFormatParam(action);
}
}

+ 0
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java View File

@@ -35,7 +35,6 @@ public class UsersWsModule extends Module {
SearchAction.class,
GroupsAction.class,
IdentityProvidersAction.class,
FavouritesWs.class,
UserPropertiesWs.class,
UserJsonWriter.class);
}

+ 24
- 0
server/sonar-server/src/main/resources/org/sonar/server/favorite/ws/search-example.json View File

@@ -0,0 +1,24 @@
{
"paging": {
"pageIndex": 1,
"pageSize": 100,
"total": 3
},
"favorites": [
{
"key": "K2",
"name": "Apache HBase",
"qualifier": "TRK"
},
{
"key": "K3",
"name": "JDK9",
"qualifier": "TRK"
},
{
"key": "K1",
"name": "Samba",
"qualifier": "TRK"
}
]
}

+ 0
- 18
server/sonar-server/src/main/resources/org/sonar/server/user/ws/favourites-index-example.xml View File

@@ -1,18 +0,0 @@
<favourites>
<favourite>
<id>2865</id>
<key>org.sonarsource.sonarqube:sonarqube</key>
<name>SonarQube</name>
<lname>SonarQube</lname>
<scope>PRJ</scope>
<qualifier>TRK</qualifier>
</favourite>
<favourite>
<id>34830</id>
<key>DEV:george.orwell@1984.com</key>
<name>George Orwell</name>
<lname>George Orwell</lname>
<scope>PRJ</scope>
<qualifier>DEV</qualifier>
</favourite>
</favourites>

+ 0
- 1
server/sonar-server/src/test/java/org/sonar/server/authentication/JwtCsrfVerifierTest.java View File

@@ -149,7 +149,6 @@ public class JwtCsrfVerifierTest {
@Test
public void ignore_rails_ws_requests() throws Exception {
executeVerifyStateDoesNotFailOnRequest("/api/events", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/favourites", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/add_comment?key=ABCD", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/delete_comment?key=ABCD", "POST");
executeVerifyStateDoesNotFailOnRequest("/api/issues/edit_comment?key=ABCD", "POST");

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java View File

@@ -30,6 +30,6 @@ public class FavoriteModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new FavoriteModule().configure(container);
assertThat(container.size()).isEqualTo(4 + 2);
assertThat(container.size()).isEqualTo(6 + 2);
}
}

+ 210
- 0
server/sonar-server/src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java View File

@@ -0,0 +1,210 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.ws;

import com.google.common.base.Throwables;
import java.io.IOException;
import java.io.InputStream;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.web.UserRole;
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.permission.UserPermissionDto;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Common.Paging;
import org.sonarqube.ws.Favorites.Favorite;
import org.sonarqube.ws.Favorites.SearchResponse;
import org.sonarqube.ws.MediaTypes;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.resources.Qualifiers.FILE;
import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newProjectDto;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.WsRequest.Method.POST;

public class SearchActionTest {
private static final int USER_ID = 123;
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone().login().setUserId(USER_ID);
@Rule
public DbTester db = DbTester.create();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();

private FavoriteFinder favoriteFinder = new FavoriteFinder(dbClient, userSession);

private WsActionTester ws = new WsActionTester(new SearchAction(favoriteFinder, dbClient, userSession));

@Test
public void return_favorites() {
ComponentDto project = newProjectDto("P1").setKey("K1").setName("N1");
addComponent(project);
addComponent(newFileDto(project).setKey("K11").setName("N11"));
addComponent(newProjectDto("P2").setKey("K2").setName("N2"));

SearchResponse result = call();

assertThat(result.getPaging())
.extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
.containsExactly(1, 100, 3);
assertThat(result.getFavoritesList())
.extracting(Favorite::getKey, Favorite::getName, Favorite::getQualifier)
.containsOnly(
tuple("K1", "N1", PROJECT),
tuple("K11", "N11", FILE),
tuple("K2", "N2", PROJECT));
}

@Test
public void empty_list() {
SearchResponse result = call();

assertThat(result.getFavoritesCount()).isEqualTo(0);
assertThat(result.getFavoritesList()).isEmpty();
}

@Test
public void filter_authorized_components() {
addComponent(newProjectDto().setKey("K1"));
ComponentDto unauthorizedProject = db.components().insertComponent(newProjectDto());
db.favorites().add(unauthorizedProject, USER_ID);

SearchResponse result = call();

assertThat(result.getFavoritesCount()).isEqualTo(1);
assertThat(result.getFavorites(0).getKey()).isEqualTo("K1");
}

@Test
public void paginate_results() {
IntStream.rangeClosed(1, 9)
.forEach(i -> addComponent(newProjectDto().setKey("K" + i).setName("N" + i)));
ComponentDto unauthorizedProject = db.components().insertComponent(newProjectDto());
db.favorites().add(unauthorizedProject, USER_ID);

SearchResponse result = call(2, 3);

assertThat(result.getFavoritesCount()).isEqualTo(3);
assertThat(result.getFavoritesList())
.extracting(Favorite::getKey)
.containsExactly("K4", "K5", "K6");

}

@Test
public void return_only_users_favorite() {
addComponent(newProjectDto().setKey("K1"));
ComponentDto otherUserFavorite = newProjectDto().setKey("K42");
db.components().insertComponent(otherUserFavorite);
db.favorites().add(otherUserFavorite, 42L);
dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto("O1", UserRole.USER, 42L, otherUserFavorite.getId()));
db.commit();

SearchResponse result = call();

assertThat(result.getFavoritesList()).extracting(Favorite::getKey).containsExactly("K1");
}

@Test
public void favorites_ordered_by_name() {
addComponent(newProjectDto().setName("N2"));
addComponent(newProjectDto().setName("N3"));
addComponent(newProjectDto().setName("N1"));

SearchResponse result = call();

assertThat(result.getFavoritesList()).extracting(Favorite::getName)
.containsExactly("N1", "N2", "N3");
}

@Test
public void json_example() {
addComponent(newProjectDto().setKey("K1").setName("Samba"));
addComponent(newProjectDto().setKey("K2").setName("Apache HBase"));
addComponent(newProjectDto().setKey("K3").setName("JDK9"));

String result = ws.newRequest().execute().getInput();

assertJson(result).isSimilarTo(getClass().getResource("search-example.json"));
}

@Test
public void definition() {
WebService.Action definition = ws.getDef();

assertThat(definition.key()).isEqualTo("search");
assertThat(definition.responseExampleAsString()).isNotEmpty();
}

@Test
public void fail_if_not_authenticated() {
userSession.anonymous();

expectedException.expect(UnauthorizedException.class);

call();
}

private void addComponent(ComponentDto component) {
db.components().insertComponent(component);
db.favorites().add(component, USER_ID);
dbClient.userPermissionDao().insert(dbSession, new UserPermissionDto("O1", UserRole.USER, USER_ID, component.getId()));
db.commit();
}

private SearchResponse call(@Nullable Integer page, @Nullable Integer pageSize) {
TestRequest request = ws.newRequest()
.setMediaType(MediaTypes.PROTOBUF)
.setMethod(POST.name());
setNullable(page, p -> request.setParam(Param.PAGE, p.toString()));
setNullable(pageSize, ps -> request.setParam(Param.PAGE_SIZE, ps.toString()));

InputStream response = request.execute().getInputStream();

try {
return SearchResponse.parseFrom(response);
} catch (IOException e) {
throw Throwables.propagate(e);
}
}

private SearchResponse call() {
return call(null, null);
}
}

+ 0
- 39
server/sonar-server/src/test/java/org/sonar/server/user/ws/FavouritesWsTest.java View File

@@ -1,39 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.ws;

import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.ws.WsTester;

import static org.assertj.core.api.Assertions.assertThat;

public class FavouritesWsTest {

WsTester tester = new WsTester(new FavouritesWs());

@Test
public void define_ws() {
WebService.Controller controller = tester.controller("api/favourites");
assertThat(controller).isNotNull();
assertThat(controller.description()).isNotEmpty();
assertThat(controller.actions()).hasSize(1);
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java View File

@@ -30,6 +30,6 @@ public class UsersWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new UsersWsModule().configure(container);
assertThat(container.size()).isEqualTo(2 + 12);
assertThat(container.size()).isEqualTo(2 + 11);
}
}

+ 0
- 89
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/favourites_controller.rb View File

@@ -1,89 +0,0 @@
#
# SonarQube, open source software quality management tool.
# Copyright (C) 2008-2016 SonarSource
# mailto:contact AT sonarsource DOT com
#
# SonarQube 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.
#
# SonarQube 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.
#

require 'json'

class Api::FavouritesController < Api::ApiController

before_filter :login_required

#
# GET /api/favourites
# curl http://localhost:9000/api/favourites -v -u admin:admin
#
def index
respond_to do |format|
format.json { render :json => jsonp(favourites_to_json(current_user.favourites)) }
format.xml { render :xml => favourites_to_xml(current_user.favourites) }
format.text { render :text => text_not_supported }
end
end

def favourites_to_json(favourites=[])
json=[]
favourites.each do |f|
json<<favourite_to_json(f)
end
json
end

def favourite_to_json(favourite)
hash={}
hash['id']=favourite.id
hash['key']=favourite.key
hash['name']=favourite.name
hash['scope']=favourite.scope
hash['branch']=favourite.branch if favourite.branch
hash['lname']=favourite.long_name if favourite.long_name
hash['lang']=favourite.language if favourite.language
hash['qualifier']=favourite.qualifier
hash
end

def favourites_to_xml(favourites, xml=Builder::XmlMarkup.new(:indent => 0))
xml.favourites do
favourites.each do |f|
xml.favourite do
xml.id(f.id)
xml.key(f.key)
xml.name(f.name)
xml.lname(f.long_name) if f.long_name
xml.branch(f.branch) if f.branch
xml.scope(f.scope)
xml.qualifier(f.qualifier)
xml.lang(f.language) if f.language
end
end
end
end

def favourite_to_xml(favourite, xml=Builder::XmlMarkup.new(:indent => 0))
xml.favourite do
xml.id(f.id)
xml.key(f.key)
xml.name(f.name)
xml.lname(f.long_name) if f.long_name
xml.branch(f.branch) if f.branch
xml.scope(f.scope)
xml.qualifier(f.qualifier)
xml.lang(f.language) if f.language
end
end
end

+ 17
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java View File

@@ -20,12 +20,17 @@

package org.sonarqube.ws.client.favorite;

import javax.annotation.Nullable;
import org.sonar.api.server.ws.WebService.Param;
import org.sonarqube.ws.Favorites.SearchResponse;
import org.sonarqube.ws.client.BaseService;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsConnector;

import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_ADD;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_REMOVE;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.ACTION_SEARCH;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.CONTROLLER_FAVORITES;
import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.PARAM_COMPONENT;

@@ -45,4 +50,16 @@ public class FavoritesService extends BaseService {

call(post);
}

public SearchResponse search(@Nullable Integer page, @Nullable Integer pageSize) {
GetRequest get = new GetRequest(path(ACTION_SEARCH));
if (page != null) {
get.setParam(Param.PAGE, page);
}
if (pageSize != null) {
get.setParam(Param.PAGE_SIZE, pageSize);
}

return call(get, SearchResponse.parser());
}
}

+ 1
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java View File

@@ -25,6 +25,7 @@ public class FavoritesWsParameters {

public static final String ACTION_ADD = "add";
public static final String ACTION_REMOVE = "remove";
public static final String ACTION_SEARCH = "search";

public static final String PARAM_COMPONENT = "component";


+ 39
- 0
sonar-ws/src/main/protobuf/ws-favorites.proto View File

@@ -0,0 +1,39 @@
// SonarQube, open source software quality management tool.
// Copyright (C) 2008-2016 SonarSource
// mailto:contact AT sonarsource DOT com
//
// SonarQube 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.
//
// SonarQube 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.

syntax = "proto2";

package sonarqube.ws.favorite;

import "ws-commons.proto";

option java_package = "org.sonarqube.ws";
option java_outer_classname = "Favorites";
option optimize_for = SPEED;

// WS api/favorites/search
message SearchResponse {
optional sonarqube.ws.commons.Paging paging = 1;
repeated Favorite favorites = 2;
}

message Favorite {
optional string key = 1;
optional string name = 2;
optional string qualifier = 3;
}

Loading…
Cancel
Save