aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorAntoine Vinot <antoine.vinot@sonarsource.com>2022-09-29 10:11:41 +0200
committersonartech <sonartech@sonarsource.com>2022-09-29 20:03:15 +0000
commit6f85804537da5fb66a091458dfdef4bd12da2572 (patch)
tree19de443b1783f1a9a7a92f7b19c73a40d5435e51 /server
parent29868c3beba7895a71597532b727d213e6320ad0 (diff)
downloadsonarqube-6f85804537da5fb66a091458dfdef4bd12da2572.tar.gz
sonarqube-6f85804537da5fb66a091458dfdef4bd12da2572.zip
SONAR-17389 - Prevent project creation with existing key but different case
Diffstat (limited to 'server')
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java7
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml8
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java21
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java12
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java32
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java4
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java8
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java8
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java8
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java22
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java23
12 files changed, 128 insertions, 29 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
index afaba3af0e2..b11d2801afd 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
@@ -241,6 +241,10 @@ public class ComponentDao implements Dao {
return Optional.ofNullable(mapper(session).selectByKey(key));
}
+ public Optional<ComponentDto> selectByKeyCaseInsensitive(DbSession session, String key) {
+ return Optional.ofNullable(mapper(session).selectByKeyCaseInsensitive(key));
+ }
+
public Optional<ComponentDto> selectByKeyAndBranch(DbSession session, String key, String branch) {
return Optional.ofNullable(mapper(session).selectBranchByKeyAndBranchKey(key, generateBranchKey(key, branch), branch));
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
index 2d827bee828..e62a69a23b7 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
@@ -31,7 +31,10 @@ import org.apache.ibatis.session.RowBounds;
public interface ComponentMapper {
@CheckForNull
- ComponentDto selectByKey(String key);
+ ComponentDto selectByKey(@Param("key") String key);
+
+ @CheckForNull
+ ComponentDto selectByKeyCaseInsensitive(@Param("key") String key);
@CheckForNull
ComponentDto selectBranchByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
@@ -40,7 +43,7 @@ public interface ComponentMapper {
ComponentDto selectPrByKeyAndBranchKey(@Param("key") String key, @Param("dbKey") String dbKey, @Param("branch") String branch);
@CheckForNull
- ComponentDto selectByUuid(String uuid);
+ ComponentDto selectByUuid(@Param("uuid") String uuid);
/**
* Return sub project of component keys
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
index 05d2e777fe1..aed19c345e9 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
@@ -32,6 +32,14 @@
p.kee=#{key,jdbcType=VARCHAR}
</select>
+ <select id="selectByKeyCaseInsensitive" parameterType="String" resultType="Component">
+ SELECT
+ <include refid="componentColumns"/>
+ FROM components p
+ where
+ lower(p.kee)=lower(#{key,jdbcType=VARCHAR})
+ </select>
+
<select id="selectBranchByKeyAndBranchKey" parameterType="String" resultType="Component">
select
<include refid="componentColumns"/>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
index c7c7d77f643..0996f6a56ff 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
@@ -1979,6 +1979,27 @@ public class ComponentDaoTest {
verifyNoInteractions(auditPersister);
}
+ @Test
+ public void selectByKeyCaseInsensitive_shouldFindProject_whenCaseIsDifferent() {
+ String projectKey = randomAlphabetic(5).toLowerCase();
+ db.components().insertPrivateProject(c -> c.setDbKey(projectKey));
+
+ ComponentDto result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey.toUpperCase()).orElse(null);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getKey()).isEqualTo(projectKey);
+ }
+
+ @Test
+ public void selectByKeyCaseInsensitive_shouldNotFindProject_whenKeyIsDifferent() {
+ String projectKey = randomAlphabetic(5).toLowerCase();
+ db.components().insertPrivateProject(c -> c.setDbKey(projectKey));
+
+ Optional<ComponentDto> result = underTest.selectByKeyCaseInsensitive(db.getSession(), projectKey + randomAlphabetic(1));
+
+ assertThat(result).isEmpty();
+ }
+
private boolean privateFlagOfUuid(String uuid) {
return underTest.selectByUuid(db.getSession(), uuid).get().isPrivate();
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java
index 00f08d4b0bd..fdf94a4c2d8 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/exceptions/BadRequestException.java
@@ -51,16 +51,20 @@ public class BadRequestException extends ServerException {
}
}
- public static BadRequestException create(List<String> errorMessages) {
- checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
- checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
- return new BadRequestException(errorMessages);
+ public static void throwBadRequestException(String message, Object... messageArguments) {
+ throw create(format(message, messageArguments));
}
public static BadRequestException create(String... errorMessages) {
return create(asList(errorMessages));
}
+ public static BadRequestException create(List<String> errorMessages) {
+ checkArgument(!errorMessages.isEmpty(), "At least one error message is required");
+ checkArgument(errorMessages.stream().noneMatch(message -> message == null || message.isEmpty()), "Message cannot be empty");
+ return new BadRequestException(errorMessages);
+ }
+
public List<String> errors() {
return errors;
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java
index 0a1ff5347ba..55c76cf9789 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ComponentUpdater.java
@@ -19,7 +19,6 @@
*/
package org.sonar.server.component;
-import com.google.common.collect.ImmutableSet;
import java.util.Date;
import java.util.Locale;
import java.util.Optional;
@@ -49,10 +48,13 @@ import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.core.component.ComponentKeys.ALLOWED_CHARACTERS_MESSAGE;
import static org.sonar.core.component.ComponentKeys.isValidProjectKey;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+import static org.sonar.server.exceptions.BadRequestException.throwBadRequestException;
public class ComponentUpdater {
- private static final Set<String> MAIN_BRANCH_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP);
+ private static final Set<String> MAIN_BRANCH_QUALIFIERS = Set.of(Qualifiers.PROJECT, Qualifiers.APP);
+ private static final String KEY_ALREADY_EXISTS_ERROR = "Could not create %s with key: \"%s\". A similar key already exists: \"%s\"";
+ private static final String MALFORMED_KEY_ERROR = "Malformed key for %s: '%s'. %s.";
private final DbClient dbClient;
private final I18n i18n;
@@ -87,6 +89,10 @@ public class ComponentUpdater {
return componentDto;
}
+ public void commitAndIndex(DbSession dbSession, ComponentDto componentDto) {
+ projectIndexers.commitAndIndexComponents(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION);
+ }
+
/**
* Create component without committing.
* Don't forget to call commitAndIndex(...) when ready to commit.
@@ -104,6 +110,8 @@ public class ComponentUpdater {
@Nullable String userUuid, @Nullable String userLogin, @Nullable String mainBranchName,
Consumer<ComponentDto> componentModifier) {
checkKeyFormat(newComponent.qualifier(), newComponent.key());
+ checkKeyAlreadyExists(dbSession, newComponent);
+
ComponentDto componentDto = createRootComponent(dbSession, newComponent, componentModifier);
if (isRootProject(componentDto)) {
createMainBranch(dbSession, componentDto.uuid(), mainBranchName);
@@ -112,14 +120,20 @@ public class ComponentUpdater {
return componentDto;
}
- public void commitAndIndex(DbSession dbSession, ComponentDto componentDto) {
- projectIndexers.commitAndIndexComponents(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION);
+ private void checkKeyFormat(String qualifier, String key) {
+ checkRequest(isValidProjectKey(key), MALFORMED_KEY_ERROR, getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE);
}
- private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, Consumer<ComponentDto> componentModifier) {
- checkRequest(!dbClient.componentDao().selectByKey(session, newComponent.key()).isPresent(),
- "Could not create %s, key already exists: %s", getQualifierToDisplay(newComponent.qualifier()), newComponent.key());
+ private void checkKeyAlreadyExists(DbSession dbSession, NewComponent newComponent) {
+ Optional<ComponentDto> componentDto = newComponent.isProject()
+ ? dbClient.componentDao().selectByKeyCaseInsensitive(dbSession, newComponent.key())
+ : dbClient.componentDao().selectByKey(dbSession, newComponent.key());
+
+ componentDto.map(ComponentDto::getKey)
+ .ifPresent(existingKey -> throwBadRequestException(KEY_ALREADY_EXISTS_ERROR, getQualifierToDisplay(newComponent.qualifier()), newComponent.key(), existingKey));
+ }
+ private ComponentDto createRootComponent(DbSession session, NewComponent newComponent, Consumer<ComponentDto> componentModifier) {
long now = system2.now();
String uuid = uuidFactory.create();
@@ -209,10 +223,6 @@ public class ComponentUpdater {
}
}
- private void checkKeyFormat(String qualifier, String key) {
- checkRequest(isValidProjectKey(key), "Malformed key for %s: '%s'. %s.", getQualifierToDisplay(qualifier), key, ALLOWED_CHARACTERS_MESSAGE);
- }
-
private String getQualifierToDisplay(String qualifier) {
return i18n.message(Locale.getDefault(), "qualifier." + qualifier, "Project");
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java
index 1710458e32d..b68a9a12e9e 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/NewComponent.java
@@ -69,6 +69,10 @@ public class NewComponent {
return description;
}
+ public boolean isProject() {
+ return PROJECT.equals(qualifier);
+ }
+
public static class Builder {
private String description;
private String key;
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java
index 6b5d25f5ca8..ca76ed7b740 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/azure/ImportAzureProjectActionTest.java
@@ -29,7 +29,6 @@ import org.sonar.alm.client.azure.GsonAzureRepo;
import org.sonar.api.config.internal.Encryption;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
@@ -47,6 +46,7 @@ import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
@@ -74,10 +74,12 @@ public class ImportAzureProjectActionTest {
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final AzureDevOpsHttpClient azureDevOpsHttpClient = mock(AzureDevOpsHttpClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final Encryption encryption = mock(Encryption.class);
@@ -252,7 +254,7 @@ public class ImportAzureProjectActionTest {
assertThatThrownBy(request::execute)
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java
index 6e50410f731..574859d8c9f 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketcloud/ImportBitbucketCloudRepoActionTest.java
@@ -29,7 +29,6 @@ import org.sonar.alm.client.bitbucket.bitbucketcloud.Project;
import org.sonar.alm.client.bitbucket.bitbucketcloud.Repository;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
@@ -47,6 +46,7 @@ import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
@@ -75,11 +75,13 @@ public class ImportBitbucketCloudRepoActionTest {
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
private final BitbucketCloudRestClient bitbucketCloudRestClient = mock(BitbucketCloudRestClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
@@ -146,7 +148,7 @@ public class ImportBitbucketCloudRepoActionTest {
assertThatThrownBy(request::execute)
.isInstanceOf(BadRequestException.class)
- .hasMessageContaining("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java
index ea7af30e4e8..97aa3145370 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/almintegration/ws/bitbucketserver/ImportBitbucketServerProjectActionTest.java
@@ -35,7 +35,6 @@ import org.sonar.alm.client.bitbucketserver.Project;
import org.sonar.alm.client.bitbucketserver.Repository;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.i18n.I18n;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbTester;
import org.sonar.db.alm.pat.AlmPatDto;
@@ -52,6 +51,7 @@ import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
import org.sonar.server.project.ProjectDefaultVisibility;
import org.sonar.server.project.Visibility;
@@ -80,11 +80,13 @@ public class ImportBitbucketServerProjectActionTest {
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create();
+ @Rule
+ public final I18nRule i18n = new I18nRule();
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class);
private final BitbucketServerRestClient bitbucketServerRestClient = mock(BitbucketServerRestClient.class);
- private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), mock(I18n.class), System2.INSTANCE,
+ private final ComponentUpdater componentUpdater = new ComponentUpdater(db.getDbClient(), i18n, System2.INSTANCE,
mock(PermissionTemplateService.class), new FavoriteUpdater(db.getDbClient()), new TestProjectIndexers(), new SequenceUuidFactory());
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession);
@@ -160,7 +162,7 @@ public class ImportBitbucketServerProjectActionTest {
.execute();
})
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create null, key already exists: " + GENERATED_PROJECT_KEY);
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", GENERATED_PROJECT_KEY, GENERATED_PROJECT_KEY);
}
@Test
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
index bb794c4c130..35b27f35e02 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
@@ -40,6 +40,7 @@ import org.sonar.server.l18n.I18nRule;
import org.sonar.server.permission.PermissionTemplateService;
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.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@@ -65,7 +66,7 @@ public class ComponentUpdaterTest {
private final TestProjectIndexers projectIndexers = new TestProjectIndexers();
private final PermissionTemplateService permissionTemplateService = mock(PermissionTemplateService.class);
- private ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2,
+ private final ComponentUpdater underTest = new ComponentUpdater(db.getDbClient(), i18n, system2,
permissionTemplateService,
new FavoriteUpdater(db.getDbClient()),
projectIndexers, new SequenceUuidFactory());
@@ -252,7 +253,7 @@ public class ComponentUpdaterTest {
.build();
assertThatThrownBy(() -> underTest.create(session, newComponent, null, null))
.isInstanceOf(BadRequestException.class)
- .hasMessage("Could not create Project, key already exists: " + existing.getDbKey());
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", existing.getDbKey(), existing.getDbKey());
}
@Test
@@ -278,4 +279,21 @@ public class ComponentUpdaterTest {
.isInstanceOf(BadRequestException.class)
.hasMessageContaining("Malformed key for Project: 'roject%Key'");
}
+
+ @Test
+ public void create_shouldFail_whenCreatingProjectWithExistingKeyButDifferentCase() {
+ String existingKey = randomAlphabetic(5).toUpperCase();
+ db.components().insertPrivateProject(component -> component.setDbKey(existingKey));
+ String newKey = existingKey.toLowerCase();
+
+ NewComponent newComponent = NewComponent.newComponentBuilder()
+ .setKey(newKey)
+ .setName(DEFAULT_PROJECT_NAME)
+ .build();
+
+ DbSession dbSession = db.getSession();
+ assertThatThrownBy(() -> underTest.create(dbSession, newComponent, null, null))
+ .isInstanceOf(BadRequestException.class)
+ .hasMessage("Could not create Project with key: \"%s\". A similar key already exists: \"%s\"", newKey, existingKey);
+ }
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java
index f0d10e6027e..5e0b7890b65 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/NewComponentTest.java
@@ -22,6 +22,7 @@ package org.sonar.server.component;
import org.junit.Test;
import static com.google.common.base.Strings.repeat;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.api.resources.Qualifiers.PROJECT;
@@ -120,8 +121,28 @@ public class NewComponentTest {
assertThat(newComponent.qualifier()).isEqualTo(PROJECT);
}
+ @Test
+ public void isProject_shouldReturnTrue_whenQualifierIsProject() {
+ NewComponent newComponent = underTest.setKey(KEY)
+ .setName(NAME)
+ .setQualifier(PROJECT)
+ .build();
+
+ assertThat(newComponent.isProject()).isTrue();
+ }
+
+ @Test
+ public void isProject_shouldReturnFalse_whenQualifierIsNotProject() {
+ NewComponent newComponent = underTest.setKey(KEY)
+ .setName(NAME)
+ .setQualifier(randomAlphabetic(4))
+ .build();
+
+ assertThat(newComponent.isProject()).isFalse();
+ }
+
private void expectBuildException(Class<? extends Exception> expectedExceptionType, String expectedMessage) {
- assertThatThrownBy(() -> underTest.build())
+ assertThatThrownBy(underTest::build)
.isInstanceOf(expectedExceptionType)
.hasMessageContaining(expectedMessage);
}