]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7288 Create WS api/favorites/remove
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 16 Dec 2016 14:27:24 +0000 (15:27 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 19 Dec 2016 09:12:54 +0000 (10:12 +0100)
it/it-tests/src/test/java/it/user/FavoritesWsTest.java
server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteModule.java
server/sonar-server/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
server/sonar-server/src/main/java/org/sonar/server/favorite/ws/RemoveAction.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/favorite/FavoriteModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/favorite/ws/RemoveActionTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/DbTester.java
sonar-db/src/test/java/org/sonar/db/favorite/FavoriteDbTester.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/favorite/FavoritesWsParameters.java
sonar-ws/src/test/java/org/sonarqube/ws/client/favorite/FavoritesServiceTest.java

index af4dd0740d1ae981b7142624eec7d286ea20333a..62ec54251e8eb89ca2c3ea78c085799d76052a3d 100644 (file)
@@ -28,7 +28,6 @@ import org.junit.ClassRule;
 import org.junit.Test;
 import org.sonar.wsclient.Sonar;
 import org.sonar.wsclient.services.Favourite;
-import org.sonar.wsclient.services.FavouriteDeleteQuery;
 import org.sonar.wsclient.services.FavouriteQuery;
 import org.sonarqube.ws.client.WsClient;
 
@@ -69,7 +68,7 @@ public class FavoritesWsTest {
     assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample", "sample:src/main/xoo/sample/Sample.xoo");
 
     // DELETE (a favorite)
-    oldWsClient.delete(new FavouriteDeleteQuery("sample"));
+    adminClient.favorites().remove("sample");
     favourites = oldWsClient.findAll(new FavouriteQuery());
     assertThat(favourites.stream().map(Favourite::getKey)).containsOnly("sample:src/main/xoo/sample/Sample.xoo");
   }
index 7107b6df851a8bc4e022ac74163abc33cbae4843..0a0da3eceddd5a0001c1e182d8f081cc5cc8671b 100644 (file)
@@ -23,6 +23,7 @@ package org.sonar.server.favorite;
 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;
 
 public class FavoriteModule extends Module {
 
@@ -31,7 +32,8 @@ public class FavoriteModule extends Module {
     add(
       FavoriteUpdater.class,
       FavoritesWs.class,
-      AddAction.class);
+      AddAction.class,
+      RemoveAction.class);
   }
 
 }
index 8aaf771f4ddaf9e5c3e4efb1f63ac3699ff9f89b..540e20fdb7f0872530ad18aad382781792f4df79 100644 (file)
@@ -22,9 +22,13 @@ package org.sonar.server.favorite;
 
 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.server.exceptions.BadRequestException;
 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";
 
@@ -39,14 +43,30 @@ public class FavoriteUpdater {
   /**
    * Set favorite to the logged in user. If no user is logged, no action is done
    */
-  public void add(DbSession dbSession, long componentDbId) {
+  public void add(DbSession dbSession, long componentDtoId) {
     if (!userSession.isLoggedIn()) {
       return;
     }
 
     dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey(PROP_FAVORITE_KEY)
-      .setResourceId(componentDbId)
+      .setResourceId(componentDtoId)
+      .setUserId(Long.valueOf(userSession.getUserId())));
+  }
+
+  /**
+   * Remove a favorite to the logged in user.
+   * @throws BadRequestException if the component is not a favorite
+   */
+  public void remove(DbSession dbSession, ComponentDto component) {
+    if (!userSession.isLoggedIn()) {
+      return;
+    }
+
+    int result = dbClient.propertiesDao().delete(dbSession, new PropertyDto()
+      .setKey(PROP_FAVORITE_KEY)
+      .setResourceId(component.getId())
       .setUserId(Long.valueOf(userSession.getUserId())));
+    checkRequest(result == 1, "Component '%s' is not a favorite", component.key());
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/RemoveAction.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/RemoveAction.java
new file mode 100644 (file)
index 0000000..32591f8
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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 java.util.function.Consumer;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.ws.KeyExamples;
+
+import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.PARAM_COMPONENT;
+
+public class RemoveAction implements FavoritesWsAction {
+  private final UserSession userSession;
+  private final DbClient dbClient;
+  private final FavoriteUpdater favoriteUpdater;
+  private final ComponentFinder componentFinder;
+
+  public RemoveAction(UserSession userSession, DbClient dbClient, FavoriteUpdater favoriteUpdater, ComponentFinder componentFinder) {
+    this.userSession = userSession;
+    this.dbClient = dbClient;
+    this.favoriteUpdater = favoriteUpdater;
+    this.componentFinder = componentFinder;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction("remove")
+      .setDescription("Remove a component (project, directory, file etc.) as favorite for the authenticated user.<br>" +
+        "Requires authentication.")
+      .setSince("6.3")
+      .setPost(true)
+      .setHandler(this);
+
+    action.createParam("component")
+      .setDescription("Component key")
+      .setRequired(true)
+      .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    removeFavorite().accept(request);
+    response.noContent();
+  }
+
+  private Consumer<Request> removeFavorite() {
+    return request -> {
+      try (DbSession dbSession = dbClient.openSession(false)) {
+        ComponentDto component = componentFinder.getByKey(dbSession, request.mandatoryParam(PARAM_COMPONENT));
+        userSession
+          .checkLoggedIn();
+        favoriteUpdater.remove(dbSession, component);
+        dbSession.commit();
+      }
+    };
+  }
+}
index 22c4bf1de6c732e293e94eacb0d941f7b5967262..1affa2ca117e0cb64364c4e40723be297e9b30e7 100644 (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(3 + 2);
+    assertThat(container.size()).isEqualTo(4 + 2);
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/RemoveActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/favorite/ws/RemoveActionTest.java
new file mode 100644 (file)
index 0000000..eb06593
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * 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 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.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static org.assertj.core.api.Assertions.assertThat;
+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.sonarqube.ws.client.favorite.FavoritesWsParameters.PARAM_COMPONENT;
+
+public class RemoveActionTest {
+  private static final String PROJECT_KEY = "project-key";
+  private static final String PROJECT_UUID = "project-uuid";
+  private static final int USER_ID = 123;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public DbTester db = DbTester.create();
+  private DbClient dbClient = db.getDbClient();
+
+  private FavoriteUpdater favoriteUpdater = new FavoriteUpdater(dbClient, userSession);
+  private WsActionTester ws = new WsActionTester(new RemoveAction(userSession, dbClient, favoriteUpdater, new ComponentFinder(dbClient)));
+
+  @Test
+  public void remove_a_favorite_project() {
+    ComponentDto project = insertProjectAndPermissions();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    db.favorites().add(project, USER_ID);
+    db.favorites().add(file, USER_ID);
+
+    TestResponse result = call(PROJECT_KEY);
+
+    assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
+    assertThat(db.favorites().hasFavorite(project, USER_ID)).isFalse();
+    assertThat(db.favorites().hasFavorite(file, USER_ID)).isTrue();
+  }
+
+  @Test
+  public void fail_if_not_already_a_favorite() {
+    insertProjectAndPermissions();
+
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Component '" + PROJECT_KEY + "' is not a favorite");
+
+    call(PROJECT_KEY);
+  }
+
+  @Test
+  public void fail_when_component_is_not_found() {
+    userSession.login();
+
+    expectedException.expect(NotFoundException.class);
+
+    call("P42");
+  }
+
+  @Test
+  public void fail_when_user_is_not_authenticated() {
+    insertProject();
+
+    expectedException.expect(UnauthorizedException.class);
+
+    call(PROJECT_KEY);
+  }
+
+  @Test
+  public void definition() {
+    WebService.Action definition = ws.getDef();
+
+    assertThat(definition.key()).isEqualTo("remove");
+    assertThat(definition.isPost()).isTrue();
+    assertThat(definition.param("component").isRequired()).isTrue();
+  }
+
+  private ComponentDto insertProject() {
+    return db.components().insertComponent(newProjectDto(PROJECT_UUID).setKey(PROJECT_KEY));
+  }
+
+  private ComponentDto insertProjectAndPermissions() {
+    userSession.login().setUserId(USER_ID);
+
+    return insertProject();
+  }
+
+  private TestResponse call(@Nullable String componentKey) {
+    TestRequest request = ws.newRequest();
+    setNullable(componentKey, c -> request.setParam(PARAM_COMPONENT, c));
+
+    return request.execute();
+  }
+}
index 9d4a3e2bd1c31ff68ec9e62e855b35a41e2257b2..48fbd8494c3d4956275a2496de7cf65a26778e7b 100644 (file)
@@ -65,6 +65,7 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.util.SequenceUuidFactory;
 import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.event.EventDbTester;
+import org.sonar.db.favorite.FavoriteDbTester;
 import org.sonar.db.issue.IssueDbTester;
 import org.sonar.db.organization.OrganizationDbTester;
 import org.sonar.db.organization.OrganizationDto;
@@ -102,6 +103,7 @@ public class DbTester extends ExternalResource {
 
   private final UserDbTester userTester;
   private final ComponentDbTester componentTester;
+  private final FavoriteDbTester favoriteTester;
   private final EventDbTester eventTester;
   private final OrganizationDbTester organizationTester;
   private final PermissionTemplateDbTester permissionTemplateTester;
@@ -116,6 +118,7 @@ public class DbTester extends ExternalResource {
     initDbClient();
     this.userTester = new UserDbTester(this);
     this.componentTester = new ComponentDbTester(this);
+    this.favoriteTester = new FavoriteDbTester(this);
     this.eventTester = new EventDbTester(this);
     this.organizationTester = new OrganizationDbTester(this);
     this.permissionTemplateTester = new PermissionTemplateDbTester(this);
@@ -190,6 +193,10 @@ public class DbTester extends ExternalResource {
     return componentTester;
   }
 
+  public FavoriteDbTester favorites() {
+    return favoriteTester;
+  }
+
   public EventDbTester events() {
     return eventTester;
   }
@@ -587,7 +594,7 @@ public class DbTester extends ExternalResource {
    */
   public void assertIndexDoesNotExist(String tableName, String indexName) {
     try (Connection connection = getConnection();
-         ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) {
+      ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) {
       List<String> indices = new ArrayList<>();
       while (rs.next()) {
         indices.add(rs.getString("INDEX_NAME").toLowerCase(Locale.ENGLISH));
diff --git a/sonar-db/src/test/java/org/sonar/db/favorite/FavoriteDbTester.java b/sonar-db/src/test/java/org/sonar/db/favorite/FavoriteDbTester.java
new file mode 100644 (file)
index 0000000..452bfba
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * 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.db.favorite;
+
+import java.util.List;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.db.property.PropertyQuery;
+
+public class FavoriteDbTester {
+  private static final String PROP_FAVORITE_KEY = "favourite";
+
+  private final DbTester db;
+  private final DbClient dbClient;
+  private final DbSession dbSession;
+
+  public FavoriteDbTester(DbTester db) {
+    this.db = db;
+    this.dbClient = db.getDbClient();
+    this.dbSession = db.getSession();
+  }
+
+  public void add(ComponentDto componentDto, long userId) {
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
+      .setKey(PROP_FAVORITE_KEY)
+      .setUserId(userId)
+      .setResourceId(componentDto.getId()));
+    dbSession.commit();
+  }
+
+  public boolean hasFavorite(ComponentDto componentDto, long userId) {
+    List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setKey(PROP_FAVORITE_KEY)
+      .setComponentId(componentDto.getId())
+      .setUserId((int) userId)
+      .build(), dbSession);
+
+    return !result.isEmpty();
+  }
+}
index dab17b133177f69e259f65fd0dbe93e895ad0863..91eede2140b96cf0ff3834dce37850b62ce7aa50 100644 (file)
@@ -25,10 +25,11 @@ 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.CONTROLLER_FAVORITES;
 import static org.sonarqube.ws.client.favorite.FavoritesWsParameters.PARAM_COMPONENT;
 
-public class FavoritesService extends BaseService{
+public class FavoritesService extends BaseService {
   public FavoritesService(WsConnector wsConnector) {
     super(wsConnector, CONTROLLER_FAVORITES);
   }
@@ -38,4 +39,10 @@ public class FavoritesService extends BaseService{
 
     call(post);
   }
+
+  public void remove(String component) {
+    PostRequest post = new PostRequest(path(ACTION_REMOVE)).setParam(PARAM_COMPONENT, component);
+
+    call(post);
+  }
 }
index 5349f5fcf341763df3b5aad017d987bb0813b10b..3c39c735ede763307c78f16a2437910117165355 100644 (file)
@@ -24,6 +24,7 @@ public class FavoritesWsParameters {
   public static final String CONTROLLER_FAVORITES = "api/favorites";
 
   public static final String ACTION_ADD = "add";
+  public static final String ACTION_REMOVE = "remove";
 
   public static final String PARAM_COMPONENT = "component";
 
index 4b6c8a40feb2ea05341d98174b9162d48d131820..b6ce72fa1da0851db5d2e8dcbafc9e477fe91112 100644 (file)
@@ -43,4 +43,14 @@ public class FavoritesServiceTest {
       .hasParam(PARAM_COMPONENT, "my_project")
       .andNoOtherParam();
   }
+
+  @Test
+  public void remove() {
+    underTest.remove("my_project");
+
+    serviceTester.assertThat(serviceTester.getPostRequest())
+      .hasPath("remove")
+      .hasParam(PARAM_COMPONENT, "my_project")
+      .andNoOtherParam();
+  }
 }