@@ -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() { |
@@ -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"); | |||
} | |||
} |
@@ -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", |
@@ -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()); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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(); | |||
}; | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -35,7 +35,6 @@ public class UsersWsModule extends Module { | |||
SearchAction.class, | |||
GroupsAction.class, | |||
IdentityProvidersAction.class, | |||
FavouritesWs.class, | |||
UserPropertiesWs.class, | |||
UserJsonWriter.class); | |||
} |
@@ -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" | |||
} | |||
] | |||
} |
@@ -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> |
@@ -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"); |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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 |
@@ -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()); | |||
} | |||
} |
@@ -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"; | |||
@@ -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; | |||
} |