diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2019-03-13 09:11:58 +0100 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-03-18 20:20:59 +0100 |
commit | 3ed96a6b902132626d4f6f4a76139613d7cbb81d (patch) | |
tree | 03a52f112331996709691f12d9f2b8aaafe838a2 | |
parent | c5ad29eb81b2d31916ba89188d6e4deed29c46a8 (diff) | |
download | sonarqube-3ed96a6b902132626d4f6f4a76139613d7cbb81d.tar.gz sonarqube-3ed96a6b902132626d4f6f4a76139613d7cbb81d.zip |
SONAR-10277 Prevent user to have more than 100 projects as favorite
17 files changed, 622 insertions, 148 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java index 04ac22cb459..619e45f2931 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java @@ -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> diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java index 7221069be0e..1dd88c185cc 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java @@ -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); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml index f1d4344765e..06fc8ddd4b1 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml @@ -128,6 +128,17 @@ 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"/> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java index 0f508819563..ec70637034e 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java @@ -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 diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java index 39de1e9f499..c03988be190 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77.java @@ -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 index 00000000000..0b9cfb08719 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundred.java @@ -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); + } + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java index 23b3f6cfc17..2732fa61caa 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DbVersion77Test.java @@ -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 index 00000000000..e6f46097168 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest.java @@ -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 index 00000000000..65a4b55c97e --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v77/DeleteFavoritesExceedingOneHundredTest/schema.sql @@ -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"); diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java b/server/sonar-server-common/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java index 7aa3aff5f3d..3e9bd57b172 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/favorite/FavoriteUpdater.java @@ -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()) diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java index f02f40bb7f9..0fcc756f13d 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/favorite/FavoriteUpdaterTest.java @@ -19,32 +19,25 @@ */ 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(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java index 0adfdb802c7..974e4c9aafa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java @@ -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 index d8ba554caf3..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/component/DefaultRubyComponentService.java +++ /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(); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java index 7523ae174db..9b08ced7c80 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/favorite/ws/AddAction.java @@ -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(); } }; diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java index 51c94ca8c69..05e26d29288 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/queue/ReportSubmitterTest.java @@ -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}")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java index 3a39e42a538..6581277f23f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java @@ -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; @@ -214,14 +216,31 @@ public class ComponentUpdaterTest { .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) + .setOrganizationUuid(db.getDefaultOrganization().getUuid()) + .build(); when(permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(eq(db.getSession()), any(ComponentDto.class))) .thenReturn(true); 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 diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java index 5e107959a07..5be7889c667 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java @@ -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 @@ -232,6 +237,41 @@ public class CreateActionTest { } @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(); userSession.addPermission(PROVISION_PROJECTS, organization); |