]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10277 Prevent user to have more than 100 projects as favorite
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 13 Mar 2019 08:11:58 +0000 (09:11 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 18 Mar 2019 19:20:59 +0000 (20:20 +0100)
17 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundred.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest/schema.sql [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java
server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java
server/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java
server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java

index 04ac22cb459d88f54c47d46e051fe669fadf61e9..619e45f29313dbf0ccf034292507547797b79580 100644 (file)
@@ -160,6 +160,10 @@ public class PropertiesDao implements Dao {
     return getMapper(session).selectByKeyAndMatchingValue(key, value);
   }
 
+  public List<PropertyDto> selectByKeyAndUserIdAndComponentQualifier(DbSession session, String key, int userId, String qualifier) {
+    return getMapper(session).selectByKeyAndUserIdAndComponentQualifier(key, userId, qualifier);
+  }
+
   /**
    * Saves the specified property and its value.
    * <p>
index 7221069be0e2ee13cf5fb277a4624e4bc49f8510..1dd88c185cc20c91d6eff1c1f9766a8eaa8b76dd 100644 (file)
@@ -38,6 +38,8 @@ public interface PropertiesMapper {
 
   List<PropertyDto> selectByKeysAndComponentIds(@Param("keys") List<String> keys, @Param("componentIds") List<Long> componentIds);
 
+  List<PropertyDto> selectByKeyAndUserIdAndComponentQualifier(@Param("key") String key, @Param("userId") int userId, @Param("qualifier") String qualifier);
+
   List<PropertyDto> selectByComponentIds(@Param("componentIds") List<Long> componentIds);
 
   List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);
index f1d4344765eb2da65748d69b8f8e335f3e64eebc..06fc8ddd4b16974c9f7573b528d00f9e9e3155c0 100644 (file)
       and p.user_id is null
   </select>
 
+  <select id="selectByKeyAndUserIdAndComponentQualifier" parameterType="map" resultType="ScrapProperty">
+    select
+    <include refid="columnsToScrapPropertyDto"/>
+    from
+    properties p
+      inner join projects prj on prj.id=p.resource_id and prj.qualifier = #{qualifier, jdbcType=VARCHAR}
+    where
+      p.prop_key = #{key, jdbcType=VARCHAR}
+      and p.user_id = #{userId, jdbcType=INTEGER}
+  </select>
+
   <select id="selectByQuery" parameterType="map" resultType="ScrapProperty">
     select
       <include refid="columnsToScrapPropertyDto"/>
index 0f5088195638e99bb6f1df7ad3b5c0caf9f2d7a4..ec70637034e6bc4cf0d18d66c9fba0fa261331f1 100644 (file)
@@ -40,6 +40,7 @@ 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.component.ComponentTesting;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.UserDto;
 
@@ -52,6 +53,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
+import static org.sonar.db.property.PropertyTesting.newPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;
 
 @RunWith(DataProviderRunner.class)
@@ -401,12 +403,33 @@ public class PropertiesDaoTest {
       newComponentPropertyDto("another key", "value", project1));
 
     assertThat(underTest.selectByKeyAndMatchingValue(db.getSession(), "key", "value"))
-    .extracting(PropertyDto::getValue, PropertyDto::getResourceId)
-    .containsExactlyInAnyOrder(
-      tuple("value", project1.getId()),
-      tuple("value", project2.getId()),
-      tuple("value", null)
-    );
+      .extracting(PropertyDto::getValue, PropertyDto::getResourceId)
+      .containsExactlyInAnyOrder(
+        tuple("value", project1.getId()),
+        tuple("value", project2.getId()),
+        tuple("value", null));
+  }
+
+  @Test
+  public void selectByKeyAndUserIdAndComponentQualifier() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+    db.properties().insertProperties(
+      newPropertyDto("key", "1", project1, user1),
+      newPropertyDto("key", "2", project2, user1),
+      newPropertyDto("key", "3", file1, user1),
+      newPropertyDto("another key", "4", project1, user1),
+      newPropertyDto("key", "5", project1, user2),
+      newGlobalPropertyDto("key", "global"));
+
+    assertThat(underTest.selectByKeyAndUserIdAndComponentQualifier(db.getSession(), "key", user1.getId(), "TRK"))
+      .extracting(PropertyDto::getValue).containsExactlyInAnyOrder("1", "2");
+    assertThat(underTest.selectByKeyAndUserIdAndComponentQualifier(db.getSession(), "key", user1.getId(), "FIL"))
+      .extracting(PropertyDto::getValue).containsExactlyInAnyOrder("3");
+    assertThat(underTest.selectByKeyAndUserIdAndComponentQualifier(db.getSession(), "key", user2.getId(), "FIL")).isEmpty();
   }
 
   @Test
index 39de1e9f499bbd2edf8c7b6826ea13f2a3f6fd01..c03988be19046eaa8f81efe3ca480afcdbe4640b 100644 (file)
@@ -35,6 +35,7 @@ public class DbVersion77 implements DbVersion {
       .add(2605, "Add SNAPSHOTS.PROJECT_VERSION", AddProjectVersionToSnapshot.class)
       .add(2606, "Drop DATA_TYPE column from FILE_SOURCES table", DropDataTypeFromFileSources.class)
       .add(2607, "Add MEMBERS_SYNC_ENABLED column to ORGANIZATIONS_ALM_BINDING table", AddMembersSyncFlagToOrgAlmBinding.class)
-      .add(2608, "Delete favorites on not supported components", DeleteFavouritesOnNotSupportedComponentQualifiers.class);
+      .add(2608, "Delete favorites on not supported components", DeleteFavouritesOnNotSupportedComponentQualifiers.class)
+      .add(2609, "Delete exceeding favorites when there are more than 100 for a user", DeleteFavoritesExceedingOneHundred.class);
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundred.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundred.java
new file mode 100644 (file)
index 0000000..0b9cfb0
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v77;
+
+import java.sql.SQLException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.SupportsBlueGreen;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.Upsert;
+
+import static java.util.Arrays.asList;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
+
+@SupportsBlueGreen
+public class DeleteFavoritesExceedingOneHundred extends DataChange {
+
+  private static final Logger LOG = Loggers.get(DeleteFavoritesExceedingOneHundred.class);
+
+  private static final String FAVOURITE_PROPERTY = "favourite";
+
+  private static final List<String> SORTED_QUALIFIERS = asList("TRK", "VW", "APP", "SVW", "FIL");
+
+  public DeleteFavoritesExceedingOneHundred(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    List<Integer> userIdsHavingMoreThanOneHundredFavourites = context.prepareSelect("SELECT user_id FROM " +
+      "(SELECT DISTINCT user_id, COUNT(id) AS nb FROM properties WHERE prop_key = ? AND user_id IS NOT NULL GROUP BY user_id) sub " +
+      "WHERE sub.nb > 100")
+      .setString(1, FAVOURITE_PROPERTY)
+      .list(row -> row.getInt(1));
+    LOG.info("Deleting favourites exceeding one hundred elements for {} users", userIdsHavingMoreThanOneHundredFavourites.size());
+    for (Integer userId : userIdsHavingMoreThanOneHundredFavourites) {
+      List<Integer> propertyIdsToKeep = context.prepareSelect("SELECT prop.id, p.qualifier, p.enabled FROM properties prop " +
+        "INNER JOIN projects p ON p.id=prop.resource_id " +
+        "WHERE prop.prop_key=? AND prop.user_id = ?")
+        .setString(1, FAVOURITE_PROPERTY)
+        .setInt(2, userId)
+        .list(Property::new)
+        .stream()
+        .sorted()
+        .map(Property::getId)
+        .limit(100)
+        .collect(toList());
+
+      String idsToString = IntStream.range(0, propertyIdsToKeep.size()).mapToObj(i -> "?").collect(Collectors.joining(","));
+      Upsert upsert = context.prepareUpsert("DELETE FROM properties WHERE prop_key=? AND user_id=? AND id NOT in (" + idsToString + ")")
+        .setString(1, FAVOURITE_PROPERTY)
+        .setInt(2, userId);
+      int index = 3;
+      for (Integer id : propertyIdsToKeep) {
+        upsert.setInt(index, id);
+        index++;
+      }
+      upsert.execute().commit();
+    }
+  }
+
+  private static class Property implements Comparable<Property> {
+    private final int id;
+    private final String qualifier;
+    private final boolean enabled;
+
+    Property(Select.Row row) throws SQLException {
+      this.id = row.getInt(1);
+      this.qualifier = row.getString(2);
+      this.enabled = row.getBoolean(3);
+    }
+
+    int getId() {
+      return id;
+    }
+
+    String getQualifier() {
+      return qualifier;
+    }
+
+    boolean isEnabled() {
+      return enabled;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Property property = (Property) o;
+      return id == property.id;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(id);
+    }
+
+    @Override
+    public int compareTo(Property o) {
+      return Comparator.comparing(Property::isEnabled)
+        .reversed()
+        .thenComparing(property -> SORTED_QUALIFIERS.indexOf(property.getQualifier()))
+        .compare(this, o);
+    }
+  }
+
+}
index 23b3f6cfc17de9ff21abb471ed31b449c17b1619..2732fa61caa163eced2926b5be061dba7e2c1293 100644 (file)
@@ -36,7 +36,7 @@ public class DbVersion77Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 9);
+    verifyMigrationCount(underTest, 10);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest.java
new file mode 100644 (file)
index 0000000..e6f4609
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v77;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.util.stream.IntStream.rangeClosed;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DeleteFavoritesExceedingOneHundredTest {
+
+  private static final String TABLE = "properties";
+  private static final String FAVOURITE_PROPERTY = "favourite";
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(DeleteFavoritesExceedingOneHundredTest.class, "schema.sql");
+
+  private DeleteFavoritesExceedingOneHundred underTest = new DeleteFavoritesExceedingOneHundred(db.database());
+
+  @Test
+  public void delete_favorites_when_user_has_more_than_100() throws SQLException {
+    int user1Id = nextInt();
+    insertProperties(user1Id, "TRK", 110);
+    int user2Id = nextInt();
+    insertProperties(user2Id, "TRK", 130);
+    int user3Id = nextInt();
+    insertProperties(user3Id, "TRK", 90);
+
+    underTest.execute();
+
+    assertFavorites(user1Id, 100);
+    assertFavorites(user2Id, 100);
+    assertFavorites(user3Id, 90);
+  }
+
+  @Test
+  public void keep_in_priority_projects_over_files() throws SQLException {
+    int userId = nextInt();
+    insertProperties(userId, "TRK", 90);
+    insertProperties(userId, "FIL", 20);
+    underTest.execute();
+
+    assertFavorites(userId, "TRK", 90);
+    assertFavorites(userId, "FIL", 10);
+  }
+
+  @Test
+  public void keep_in_priority_views_over_sub_views() throws SQLException {
+    int userId = nextInt();
+    insertProperties(userId, "VW", 90);
+    insertProperties(userId, "SVW", 20);
+    underTest.execute();
+
+    assertFavorites(userId, "VW", 90);
+    assertFavorites(userId, "SVW", 10);
+  }
+
+  @Test
+  public void keep_in_priority_apps_over_sub_views() throws SQLException {
+    int userId = nextInt();
+    insertProperties(userId, "APP", 90);
+    insertProperties(userId, "SVW", 20);
+    underTest.execute();
+
+    assertFavorites(userId, "APP", 90);
+    assertFavorites(userId, "SVW", 10);
+  }
+
+  @Test
+  public void keep_in_priority_enabled_projects() throws SQLException {
+    int userId = nextInt();
+    insertProperties(userId, "TRK", 90);
+    rangeClosed(1, 20).forEach(i -> {
+      int componentId = insertDisabledComponent("TRK");
+      insertProperty(FAVOURITE_PROPERTY, userId, componentId);
+    });
+
+    underTest.execute();
+
+    assertFavorites(userId, "TRK", true, 90);
+    assertFavorites(userId, "TRK", false, 10);
+  }
+
+  @Test
+  public void ignore_non_favorite_properties() throws SQLException {
+    int userId = nextInt();
+    rangeClosed(1, 130).forEach(i -> {
+      int componentId = insertComponent("TRK");
+      insertProperty("other", userId, componentId);
+    });
+
+    underTest.execute();
+
+    assertProperties("other", userId, 130);
+  }
+
+  private void assertFavorites(int userId, int expectedSize) {
+    assertProperties(FAVOURITE_PROPERTY, userId, expectedSize);
+  }
+
+  private void assertFavorites(int userId, String qualifier, int expectedSize) {
+    assertThat(db.countSql("SELECT count(prop.id) FROM properties prop " +
+      "INNER JOIN projects p ON p.id=prop.resource_id AND p.qualifier='" + qualifier + "'" +
+      "WHERE prop.user_id=" + userId + " AND prop.prop_key='" + FAVOURITE_PROPERTY + "'"))
+        .isEqualTo(expectedSize);
+  }
+
+  private void assertFavorites(int userId, String qualifier, boolean enabled, int expectedSize) {
+    assertThat(db.countSql("SELECT count(prop.id) FROM properties prop " +
+      "INNER JOIN projects p ON p.id=prop.resource_id AND p.qualifier='" + qualifier + "' AND p.enabled='" + enabled + "'" +
+      "WHERE prop.user_id=" + userId + " AND prop.prop_key='" + FAVOURITE_PROPERTY + "'"))
+        .isEqualTo(expectedSize);
+  }
+
+  private void assertProperties(String propertyKey, int userId, int expectedSize) {
+    assertThat(db.countSql("SELECT count(ID) FROM properties WHERE user_id=" + userId + " AND prop_key='" + propertyKey + "'")).isEqualTo(expectedSize);
+  }
+
+  private void insertProperties(int userId, String qualifier, int nbProperties) {
+    rangeClosed(1, nbProperties).forEach(i -> {
+      int componentId = insertComponent(qualifier);
+      insertProperty(FAVOURITE_PROPERTY, userId, componentId);
+    });
+  }
+
+  private void insertProperty(String key, int userId, int componentId) {
+    db.executeInsert(
+      TABLE,
+      "PROP_KEY", key,
+      "USER_ID", userId,
+      "RESOURCE_ID", componentId,
+      "IS_EMPTY", true,
+      "CREATED_AT", 123456);
+  }
+
+  private int insertComponent(String qualifier) {
+    return insertCommonComponent(qualifier, true);
+  }
+
+  private int insertDisabledComponent(String qualifier) {
+    return insertCommonComponent(qualifier, false);
+  }
+
+  private int insertCommonComponent(String qualifier, boolean enabled) {
+    int id = nextInt();
+    String uuid = "uuid_" + id;
+    db.executeInsert(
+      "projects",
+      "ID", id,
+      "UUID", uuid,
+      "ORGANIZATION_UUID", "org_" + id,
+      "UUID_PATH", "path_" + id,
+      "ROOT_UUID", "root_" + id,
+      "PROJECT_UUID", "project_" + id,
+      "QUALIFIER", qualifier,
+      "ENABLED", enabled,
+      "PRIVATE", false);
+    return id;
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest/schema.sql
new file mode 100644 (file)
index 0000000..65a4b55
--- /dev/null
@@ -0,0 +1,59 @@
+CREATE TABLE "PROPERTIES" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "PROP_KEY" VARCHAR(512) NOT NULL,
+  "RESOURCE_ID" INTEGER,
+  "USER_ID" INTEGER,
+  "IS_EMPTY" BOOLEAN NOT NULL,
+  "TEXT_VALUE" VARCHAR(4000),
+  "CLOB_VALUE" CLOB,
+  "CREATED_AT" BIGINT
+);
+CREATE INDEX "PROPERTIES_KEY" ON "PROPERTIES" ("PROP_KEY");
+
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(400),
+  "UUID" VARCHAR(50) NOT NULL,
+  "UUID_PATH" VARCHAR(1500) NOT NULL,
+  "ROOT_UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50) NOT NULL,
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(1500),
+  "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+  "NAME" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(2000),
+  "PRIVATE" BOOLEAN NOT NULL,
+  "TAGS" VARCHAR(500),
+  "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "DEPRECATED_KEE" VARCHAR(400),
+  "PATH" VARCHAR(2000),
+  "LANGUAGE" VARCHAR(20),
+  "COPY_COMPONENT_UUID" VARCHAR(50),
+  "LONG_NAME" VARCHAR(2000),
+  "DEVELOPER_UUID" VARCHAR(50),
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT,
+  "B_CHANGED" BOOLEAN,
+  "B_COPY_COMPONENT_UUID" VARCHAR(50),
+  "B_DESCRIPTION" VARCHAR(2000),
+  "B_ENABLED" BOOLEAN,
+  "B_UUID_PATH" VARCHAR(1500),
+  "B_LANGUAGE" VARCHAR(20),
+  "B_LONG_NAME" VARCHAR(500),
+  "B_MODULE_UUID" VARCHAR(50),
+  "B_MODULE_UUID_PATH" VARCHAR(1500),
+  "B_NAME" VARCHAR(500),
+  "B_PATH" VARCHAR(2000),
+  "B_QUALIFIER" VARCHAR(10)
+);
+
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
index 7aa3aff5f3d2345a9f40d07244eb56d5b6be7bd9..3e9bd57b1723fad053e1e4e2d36968ff9ee813e5 100644 (file)
@@ -41,7 +41,7 @@ public class FavoriteUpdater {
   /**
    * Set favorite to the logged in user. If no user, no action is done
    */
-  public void add(DbSession dbSession, ComponentDto componentDto, @Nullable Integer userId) {
+  public void add(DbSession dbSession, ComponentDto componentDto, @Nullable Integer userId, boolean failIfTooManyFavorites) {
     if (userId == null) {
       return;
     }
@@ -52,6 +52,12 @@ public class FavoriteUpdater {
       .setComponentId(componentDto.getId())
       .build(), dbSession);
     checkArgument(existingFavoriteOnComponent.isEmpty(), "Component '%s' is already a favorite", componentDto.getDbKey());
+
+    List<PropertyDto> existingFavorites = dbClient.propertiesDao().selectByKeyAndUserIdAndComponentQualifier(dbSession, PROP_FAVORITE_KEY, userId, componentDto.qualifier());
+    if (existingFavorites.size() >= 100) {
+      checkArgument(!failIfTooManyFavorites, "You cannot have more than 100 favorites on components with qualifier '%s'", componentDto.qualifier());
+      return;
+    }
     dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto()
       .setKey(PROP_FAVORITE_KEY)
       .setResourceId(componentDto.getId())
index f02f40bb7f9c8ce27a9d73c6de651f09d53e74e6..0fcc756f13d3bf1abccdbbeb3599951ac061f0e5 100644 (file)
  */
 package org.sonar.server.favorite;
 
+import java.util.stream.IntStream;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.System2;
 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.component.ComponentTesting;
-import org.sonar.db.organization.OrganizationTesting;
 import org.sonar.db.property.PropertyQuery;
+import org.sonar.db.user.UserDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class FavoriteUpdaterTest {
-  private static final long COMPONENT_ID = 23L;
-  private static final String COMPONENT_KEY = "K1";
-  private static final ComponentDto COMPONENT = ComponentTesting.newPrivateProjectDto(OrganizationTesting.newOrganizationDto())
-    .setId(COMPONENT_ID)
-    .setDbKey(COMPONENT_KEY);
-  private static final int USER_ID = 42;
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
   @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
+  public DbTester db = DbTester.create();
 
   private DbClient dbClient = db.getDbClient();
   private DbSession dbSession = db.getSession();
@@ -53,42 +46,94 @@ public class FavoriteUpdaterTest {
 
   @Test
   public void put_favorite() {
-    assertNoFavorite();
+    ComponentDto project = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
+    assertNoFavorite(project, user);
 
-    underTest.add(dbSession, COMPONENT, USER_ID);
+    underTest.add(dbSession, project, user.getId(), true);
 
-    assertFavorite();
+    assertFavorite(project, user);
   }
 
   @Test
   public void do_nothing_when_no_user() {
-    underTest.add(dbSession, COMPONENT, null);
+    ComponentDto project = db.components().insertPrivateProject();
 
-    assertNoFavorite();
+    underTest.add(dbSession, project, null, true);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setComponentId(project.getId())
+      .build(), dbSession)).isEmpty();
+  }
+
+  @Test
+  public void do_not_add_favorite_when_already_100_favorite_projects() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserId(user.getId())
+      .build(), dbSession)).hasSize(100);
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.add(dbSession, project, user.getId(), false);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserId(user.getId())
+      .build(), dbSession)).hasSize(100);
+  }
+
+  @Test
+  public void do_not_add_favorite_when_already_100_favorite_portfolios() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserId(user.getId())
+      .build(), dbSession)).hasSize(100);
+    ComponentDto project = db.components().insertPrivateProject();
+
+    underTest.add(dbSession, project, user.getId(), false);
+
+    assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setUserId(user.getId())
+      .build(), dbSession)).hasSize(100);
+  }
+
+  @Test
+  public void fail_when_more_than_100_projects_favorites() {
+    UserDto user = db.users().insertUser();
+    IntStream.rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
+    ComponentDto project = db.components().insertPrivateProject();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("You cannot have more than 100 favorites on components with qualifier 'TRK'");
+
+    underTest.add(dbSession, project, user.getId(), true);
   }
 
   @Test
   public void fail_when_adding_existing_favorite() {
-    underTest.add(dbSession, COMPONENT, USER_ID);
-    assertFavorite();
+    ComponentDto project = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
+    underTest.add(dbSession, project, user.getId(), true);
+    assertFavorite(project, user);
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Component 'K1' is already a favorite");
+    expectedException.expectMessage(String.format("Component '%s' is already a favorite", project.getKey()));
 
-    underTest.add(dbSession, COMPONENT, USER_ID);
+    underTest.add(dbSession, project, user.getId(), true);
   }
 
-  private void assertFavorite() {
+  private void assertFavorite(ComponentDto project, UserDto user) {
     assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserId(USER_ID)
-      .setComponentId(COMPONENT_ID)
+      .setUserId(user.getId())
+      .setComponentId(project.getId())
       .build(), dbSession)).hasSize(1);
   }
 
-  private void assertNoFavorite() {
+  private void assertNoFavorite(ComponentDto project, UserDto user) {
     assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
-      .setUserId(USER_ID)
-      .setComponentId(COMPONENT_ID)
+      .setUserId(user.getId())
+      .setComponentId(project.getId())
       .build(), dbSession)).isEmpty();
   }
 }
index 0adfdb802c79b0542081e3af83a2eba7a18171db..974e4c9aafa639ebe62491d7808de6c4785f80c5 100644 (file)
@@ -72,7 +72,7 @@ public class ComponentUpdater {
    * - Create component
    * - Apply default permission template
    * - Add component to favorite if the component has the 'Project Creators' permission
-   * - Index component if es indexes
+   * - Index component in es indexes
    */
   public ComponentDto create(DbSession dbSession, NewComponent newComponent, @Nullable Integer userId) {
     ComponentDto componentDto = createWithoutCommit(dbSession, newComponent, userId);
@@ -131,16 +131,14 @@ public class ComponentUpdater {
       && MAIN_BRANCH_QUALIFIERS.contains(componentDto.qualifier());
   }
 
-  private BranchDto createMainBranch(DbSession session, String componentUuid) {
+  private void createMainBranch(DbSession session, String componentUuid) {
     BranchDto branch = new BranchDto()
       .setBranchType(BranchType.LONG)
       .setUuid(componentUuid)
       .setKey(BranchDto.DEFAULT_MAIN_BRANCH_NAME)
       .setMergeBranchUuid(null)
       .setProjectUuid(componentUuid);
-
     dbClient.branchDao().upsert(session, branch);
-    return branch;
   }
 
   /**
@@ -160,7 +158,7 @@ public class ComponentUpdater {
     permissionTemplateService.applyDefault(dbSession, componentDto, userId);
     if (componentDto.qualifier().equals(PROJECT)
       && permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(dbSession, componentDto)) {
-      favoriteUpdater.add(dbSession, componentDto, userId);
+      favoriteUpdater.add(dbSession, componentDto, userId, false);
     }
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java b/server/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java
deleted file mode 100644 (file)
index d8ba554..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.server.component;
-
-import org.sonar.api.server.ServerSide;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.user.UserSession;
-
-import static org.sonar.server.component.NewComponent.newComponentBuilder;
-
-/**
- * Used in GOV
- */
-@ServerSide
-public class DefaultRubyComponentService {
-
-  private final UserSession userSession;
-  private final DbClient dbClient;
-  private final ComponentUpdater componentUpdater;
-  private final DefaultOrganizationProvider defaultOrganizationProvider;
-
-  public DefaultRubyComponentService(UserSession userSession, DbClient dbClient, ComponentUpdater componentUpdater,
-    DefaultOrganizationProvider defaultOrganizationProvider) {
-    this.userSession = userSession;
-    this.dbClient = dbClient;
-    this.componentUpdater = componentUpdater;
-    this.defaultOrganizationProvider = defaultOrganizationProvider;
-  }
-
-  // Used in GOV
-  /**
-   * @deprecated Use {@link ComponentUpdater#create(DbSession, NewComponent, Integer)} instead
-   */
-  @Deprecated
-  public Long createComponent(String key, String name, String qualifier) {
-    try (DbSession dbSession = dbClient.openSession(false)) {
-      NewComponent newComponent = newComponentBuilder()
-        .setOrganizationUuid(defaultOrganizationProvider.get().getUuid())
-        .setKey(key)
-        .setName(name)
-        .setQualifier(qualifier)
-        .build();
-      return componentUpdater.create(
-        dbSession,
-        newComponent,
-        userSession.isLoggedIn() ? userSession.getUserId() : null).getId();
-    }
-  }
-
-}
index 7523ae174db85f4c8cd3f8a39a54d6ec0578c31f..9b08ced7c80f5133b37c9a5bda241a9d96e63490 100644 (file)
@@ -61,10 +61,12 @@ public class AddAction implements FavoritesWsAction {
   public void define(WebService.NewController context) {
     WebService.NewAction action = context.createAction("add")
       .setDescription("Add a component (project, file etc.) as favorite for the authenticated user.<br>" +
+        "Only 100 components by qualifier can be added as favorite.<br>" +
         "Requires authentication and the following permission: 'Browse' on the project of the specified component.")
       .setSince("6.3")
       .setChangelog(
-        new Change("7.7", "It's no more possible to set a directory as favorite"),
+        new Change("7.7", "It's no longer possible to have more than 100 favorites by qualifier"),
+        new Change("7.7", "It's no longer possible to set a directory as favorite"),
         new Change("7.6", format("The use of module keys in parameter '%s' is deprecated", PARAM_COMPONENT)))
       .setPost(true)
       .setHandler(this);
@@ -89,7 +91,7 @@ public class AddAction implements FavoritesWsAction {
         userSession
           .checkLoggedIn()
           .checkComponentPermission(USER, componentDto);
-        favoriteUpdater.add(dbSession, componentDto, userSession.isLoggedIn() ? userSession.getUserId() : null);
+        favoriteUpdater.add(dbSession, componentDto, userSession.isLoggedIn() ? userSession.getUserId() : null, true);
         dbSession.commit();
       }
     };
index 51c94ca8c69c85f8e62b8de190fa1ac2bd42c18e..05e26d2928859b9e02bf0f0aa2bc110ca1ea00e0 100644 (file)
@@ -29,6 +29,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.i18n.I18n;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.queue.CeQueue;
 import org.sonar.ce.queue.CeQueueImpl;
@@ -43,7 +44,7 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentUpdater;
-import org.sonar.server.component.NewComponent;
+import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
@@ -54,13 +55,13 @@ import org.sonar.server.tester.UserSessionRule;
 import static java.lang.String.format;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Collections.emptyMap;
+import static java.util.stream.IntStream.rangeClosed;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -68,7 +69,6 @@ import static org.mockito.Mockito.when;
 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
 import static org.sonar.db.permission.OrganizationPermission.SCAN;
 
@@ -84,14 +84,16 @@ public class ReportSubmitterTest {
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
   @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
+  public DbTester db = DbTester.create();
 
   private String defaultOrganizationKey;
   private String defaultOrganizationUuid;
+
   private CeQueue queue = mock(CeQueueImpl.class);
-  private ComponentUpdater componentUpdater = mock(ComponentUpdater.class);
+  private TestProjectIndexers projectIndexers = new TestProjectIndexers();
   private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
-  private FavoriteUpdater favoriteUpdater = mock(FavoriteUpdater.class);
+  private ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), mock(System2.class), permissionTemplateService,
+    new FavoriteUpdater(db.getDbClient()), projectIndexers);
   private BranchSupport ossEditionBranchSupport = new BranchSupport();
 
   private ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport);
@@ -107,10 +109,7 @@ public class ReportSubmitterTest {
     userSession
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
-
-    ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
     mockSuccessfulPrepareSubmitCall();
-    when(componentUpdater.create(any(), any(), any())).thenReturn(project);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
       .thenReturn(true);
     Map<String, String> nonEmptyCharacteristics = IntStream.range(0, 1 + new Random().nextInt(5))
@@ -129,17 +128,13 @@ public class ReportSubmitterTest {
     userSession
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
-
-    ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
     mockSuccessfulPrepareSubmitCall();
-    when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
       .thenReturn(true);
 
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8));
 
     verifyReportIsPersisted(TASK_UUID);
-    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
   }
 
   @Test
@@ -153,7 +148,6 @@ public class ReportSubmitterTest {
 
     verifyReportIsPersisted(TASK_UUID);
     verifyZeroInteractions(permissionTemplateService);
-    verifyZeroInteractions(favoriteUpdater);
     verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
       && submit.getComponent().filter(cpt -> cpt.getUuid().equals(project.uuid()) && cpt.getMainComponentUuid().equals(project.uuid())).isPresent()
       && submit.getSubmitterUuid().equals(user.getUuid())
@@ -166,32 +160,42 @@ public class ReportSubmitterTest {
     userSession
       .addPermission(OrganizationPermission.SCAN, organization.getUuid())
       .addPermission(PROVISION_PROJECTS, organization);
-
     mockSuccessfulPrepareSubmitCall();
-    ComponentDto createdProject = newPrivateProjectDto(organization, PROJECT_UUID).setDbKey(PROJECT_KEY);
-    when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
-    when(
-      permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY)))
-        .thenReturn(true);
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
     when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);
 
     underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
+    ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
     verifyReportIsPersisted(TASK_UUID);
     verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT)
-      && submit.getComponent().filter(cpt -> cpt.getUuid().equals(PROJECT_UUID) && cpt.getMainComponentUuid().equals(PROJECT_UUID)).isPresent()
+      && submit.getComponent().filter(cpt -> cpt.getUuid().equals(createdProject.uuid()) && cpt.getMainComponentUuid().equals(createdProject.uuid())).isPresent()
       && submit.getUuid().equals(TASK_UUID)));
-    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
   }
 
   @Test
-  public void no_favorite_when_no_project_creator_permission_on_permission_template() {
+  public void add_project_as_favorite_when_project_creator_permission_on_permission_template() {
+    UserDto user = db.users().insertUser();
+    OrganizationDto organization = db.organizations().insert();
+    userSession
+      .logIn(user)
+      .addPermission(OrganizationPermission.SCAN, organization.getUuid())
+      .addPermission(PROVISION_PROJECTS, organization);
+    mockSuccessfulPrepareSubmitCall();
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
+    when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);
+
+    underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
+
+    ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
+    assertThat(db.favorites().hasFavorite(createdProject, user.getId())).isTrue();
+  }
+
+  @Test
+  public void do_no_add_favorite_when_no_project_creator_permission_on_permission_template() {
     userSession
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
-
-    ComponentDto createdProject = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
-    when(componentUpdater.createWithoutCommit(any(), any(), isNull())).thenReturn(createdProject);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
       .thenReturn(true);
     when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(false);
@@ -199,8 +203,27 @@ public class ReportSubmitterTest {
 
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
-    verifyZeroInteractions(favoriteUpdater);
-    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(createdProject));
+    ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
+    assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue();
+  }
+
+  @Test
+  public void do_no_add_favorite_when_already_100_favorite_projects_and_no_project_creator_permission_on_permission_template() {
+    UserDto user = db.users().insertUser();
+    rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
+    OrganizationDto organization = db.organizations().insert();
+    userSession
+      .logIn(user)
+      .addPermission(OrganizationPermission.SCAN, organization.getUuid())
+      .addPermission(PROVISION_PROJECTS, organization);
+    mockSuccessfulPrepareSubmitCall();
+    when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(organization.getUuid()), any(), eq(PROJECT_KEY))).thenReturn(true);
+    when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);
+
+    underTest.submit(organization.getKey(), PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
+
+    ComponentDto createdProject = db.getDbClient().componentDao().selectByKey(db.getSession(), PROJECT_KEY).get();
+    assertThat(db.favorites().hasNoFavorite(createdProject)).isTrue();
   }
 
   @Test
@@ -208,17 +231,13 @@ public class ReportSubmitterTest {
     userSession
       .addPermission(OrganizationPermission.SCAN, db.getDefaultOrganization().getUuid())
       .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization());
-
-    ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID).setDbKey(PROJECT_KEY);
     mockSuccessfulPrepareSubmitCall();
-    when(componentUpdater.createWithoutCommit(any(), any(), any())).thenReturn(project);
     when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), eq(defaultOrganizationUuid), any(), eq(PROJECT_KEY)))
       .thenReturn(true);
 
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
 
     verify(queue).submit(any(CeTaskSubmit.class));
-    verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(project));
   }
 
   @Test
@@ -250,10 +269,8 @@ public class ReportSubmitterTest {
   @Test
   public void project_branch_must_not_benefit_from_the_scan_permission_on_main_project() {
     String branchName = "branchFoo";
-
     ComponentDto mainProject = db.components().insertPrivateProject();
     userSession.addProjectPermission(GlobalPermissions.SCAN_EXECUTION, mainProject);
-
     // user does not have the "scan" permission on the branch, so it can't scan it
     ComponentDto branchProject = db.components().insertPrivateProject(p -> p.setDbKey(mainProject.getDbKey() + ":" + branchName));
 
@@ -321,7 +338,6 @@ public class ReportSubmitterTest {
     ComponentDto component = ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization(), PROJECT_UUID);
     userSession.addProjectPermission(SCAN_EXECUTION, component);
     mockSuccessfulPrepareSubmitCall();
-    when(componentUpdater.create(any(DbSession.class), any(NewComponent.class), eq(null))).thenReturn(new ComponentDto().setUuid(PROJECT_UUID).setDbKey(PROJECT_KEY));
 
     expectedException.expect(ForbiddenException.class);
     underTest.submit(defaultOrganizationKey, PROJECT_KEY, null, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}"));
index 3a39e42a53834b825324e6bfeee661af3582ba91..6581277f23f9768882f357b3501255f0bf3c6bca 100644 (file)
@@ -26,6 +26,7 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.Scopes;
 import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.BranchType;
@@ -39,6 +40,7 @@ import org.sonar.server.favorite.FavoriteUpdater;
 import org.sonar.server.l18n.I18nRule;
 import org.sonar.server.permission.PermissionTemplateService;
 
+import static java.util.stream.IntStream.rangeClosed;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -209,6 +211,23 @@ public class ComponentUpdaterTest {
   @Test
   public void add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template() {
     UserDto userDto = db.users().insertUser();
+    NewComponent project = NewComponent.newComponentBuilder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .setOrganizationUuid(db.getDefaultOrganization().getUuid())
+      .build();
+    when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class)))
+      .thenReturn(true);
+
+    ComponentDto dto = underTest.create(db.getSession(), project, userDto.getId());
+
+    assertThat(db.favorites().hasFavorite(dto, userDto.getId())).isTrue();
+  }
+
+  @Test
+  public void do_not_add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template_and_already_100_favorites() {
+    UserDto user = db.users().insertUser();
+    rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
     NewComponent project = NewComponent.newComponentBuilder()
       .setKey(DEFAULT_PROJECT_KEY)
       .setName(DEFAULT_PROJECT_NAME)
@@ -219,9 +238,9 @@ public class ComponentUpdaterTest {
 
     ComponentDto dto = underTest.create(db.getSession(),
       project,
-      userDto.getId());
+      user.getId());
 
-    assertThat(db.favorites().hasFavorite(dto, userDto.getId())).isTrue();
+    assertThat(db.favorites().hasFavorite(dto, user.getId())).isFalse();
   }
 
   @Test
index 5e107959a0791ffd05f1a347f2efe2d26df86581..5be7889c66724f73acdc08c42d520b1a42645df3 100644 (file)
@@ -25,9 +25,11 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentUpdater;
 import org.sonar.server.es.TestProjectIndexers;
 import org.sonar.server.exceptions.BadRequestException;
@@ -48,11 +50,13 @@ import org.sonarqube.ws.Projects.CreateWsResponse;
 import org.sonarqube.ws.Projects.CreateWsResponse.Project;
 
 import static java.util.Optional.ofNullable;
+import static java.util.stream.IntStream.rangeClosed;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
 import static org.sonar.server.project.Visibility.PRIVATE;
 import static org.sonar.server.project.ws.ProjectsWsSupport.PARAM_ORGANIZATION;
@@ -82,11 +86,12 @@ public class CreateActionTest {
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
   private BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class);
   private TestProjectIndexers projectIndexers = new TestProjectIndexers();
+  private PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
   private WsActionTester ws = new WsActionTester(
     new CreateAction(
       new ProjectsWsSupport(db.getDbClient(), defaultOrganizationProvider, billingValidations),
       db.getDbClient(), userSession,
-      new ComponentUpdater(db.getDbClient(), i18n, system2, mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()),
+      new ComponentUpdater(db.getDbClient(), i18n, system2, permissionTemplateService, new FavoriteUpdater(db.getDbClient()),
         projectIndexers)));
 
   @Test
@@ -231,6 +236,41 @@ public class CreateActionTest {
       .isEqualTo(Strings.repeat("a", 497) + "...");
   }
 
+  @Test
+  public void add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template() {
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);
+    userSession.logIn(user).addPermission(PROVISION_PROJECTS, organization);
+
+    ws.newRequest()
+      .setParam("key", DEFAULT_PROJECT_KEY)
+      .setParam("name", DEFAULT_PROJECT_NAME)
+      .setParam("organization", organization.getKey())
+      .executeProtobuf(CreateWsResponse.class);
+
+    ComponentDto project = db.getDbClient().componentDao().selectByKey(db.getSession(), DEFAULT_PROJECT_KEY).get();
+    assertThat(db.favorites().hasFavorite(project, user.getId())).isTrue();
+  }
+
+  @Test
+  public void do_not_add_project_to_user_favorites_if_project_creator_is_defined_in_permission_template_and_already_100_favorites() {
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(any(DbSession.class), any(ComponentDto.class))).thenReturn(true);
+    rangeClosed(1, 100).forEach(i -> db.favorites().add(db.components().insertPrivateProject(), user.getId()));
+    userSession.logIn(user).addPermission(PROVISION_PROJECTS, organization);
+
+    ws.newRequest()
+      .setParam("key", DEFAULT_PROJECT_KEY)
+      .setParam("name", DEFAULT_PROJECT_NAME)
+      .setParam("organization", organization.getKey())
+      .executeProtobuf(CreateWsResponse.class);
+
+    ComponentDto project = db.getDbClient().componentDao().selectByKey(db.getSession(), DEFAULT_PROJECT_KEY).get();
+    assertThat(db.favorites().hasNoFavorite(project)).isTrue();
+  }
+
   @Test
   public void fail_to_create_private_projects_when_organization_is_not_allowed_to_use_private_projects() {
     OrganizationDto organization = db.organizations().insert();