aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2015-06-01 13:32:17 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2015-06-01 13:40:57 +0200
commit90afae1acb238433d0e0e783168c93308ea3e7a5 (patch)
tree547e7785814d0576f0685e44fe60bbec7dd32d78 /server
parente02030e1165aeafbeb9e4479caa32279bb4129a2 (diff)
downloadsonarqube-90afae1acb238433d0e0e783168c93308ea3e7a5.tar.gz
sonarqube-90afae1acb238433d0e0e783168c93308ea3e7a5.zip
SONAR-6259 Fix validation of project and module keys
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java188
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java32
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java299
6 files changed, 515 insertions, 36 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
index fdebb5a6f69..52169547e35 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java
@@ -22,6 +22,12 @@ package org.sonar.server.component.db;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.apache.ibatis.session.RowBounds;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
@@ -36,14 +42,6 @@ import org.sonar.core.persistence.DbSession;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.NotFoundException;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
import static com.google.common.collect.Maps.newHashMapWithExpectedSize;
/**
@@ -82,10 +80,6 @@ public class ComponentDao implements DaoComponent {
return mapper(session).countById(id) > 0;
}
- public List<ComponentDto> selectModulesByProject(String projectKey, DbSession session) {
- return mapper(session).selectModulesByProject(projectKey);
- }
-
public List<ComponentDto> selectSubProjectsByComponentUuids(DbSession session, Collection<String> keys) {
if (keys.isEmpty()) {
return Collections.emptyList();
@@ -137,7 +131,11 @@ public class ComponentDao implements DaoComponent {
}
public List<ComponentDto> selectComponentsFromProjectKey(DbSession session, String projectKey) {
- return mapper(session).selectComponentsFromProjectKey(projectKey);
+ return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, null);
+ }
+
+ public List<ComponentDto> selectModulesFromProjectKey(DbSession session, String projectKey) {
+ return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT);
}
public List<ComponentDto> selectByKeys(DbSession session, Collection<String> keys) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
index 9f3368cff76..c1052d65fdf 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
@@ -37,6 +37,7 @@ public class ComputationSteps {
public static List<Class<? extends ComputationStep>> orderedStepClasses() {
return Arrays.asList(
PopulateComponentsUuidAndKeyStep.class,
+ ValidateProjectStep.class,
// Read report
ParseReportStep.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java
new file mode 100644
index 00000000000..92409fc2acf
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ValidateProjectStep.java
@@ -0,0 +1,188 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.ComponentKeys;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor;
+import org.sonar.server.db.DbClient;
+
+/**
+ * Validate project and modules. It will fail in the following cases :
+ * <ol>
+ * <li>property {@link org.sonar.api.CoreProperties#CORE_PREVENT_AUTOMATIC_PROJECT_CREATION} is set to true and project does not exists</li>
+ * <li>branch is not valid</li>
+ * <li>project or module key is not valid</li>
+ * <li>module key already exists in another project (same module key cannot exists in different projects)</li>
+ * <li>module key is already used as a project key</li>
+ * </ol>
+ */
+public class ValidateProjectStep implements ComputationStep {
+
+ private static final Joiner MESSAGES_JOINER = Joiner.on("\n o ");
+
+ private final DbClient dbClient;
+ private final Settings settings;
+
+ public ValidateProjectStep(DbClient dbClient, Settings settings) {
+ this.dbClient = dbClient;
+ this.settings = settings;
+ }
+
+ @Override
+ public void execute(ComputationContext context) {
+ DbSession session = dbClient.openSession(false);
+ try {
+ List<ComponentDto> modules = dbClient.componentDao().selectModulesFromProjectKey(session, context.getRoot().getKey());
+ Map<String, ComponentDto> modulesByKey = Maps.uniqueIndex(modules, new Function<ComponentDto, String>() {
+ @Override
+ public String apply(@Nonnull ComponentDto input) {
+ return input.key();
+ }
+ });
+ ValidateProjectsVisitor visitor = new ValidateProjectsVisitor(session, dbClient.componentDao(), context.getReportMetadata(),
+ context.getReportReader(), settings.getBoolean(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION), modulesByKey);
+ visitor.visit(context.getRoot());
+
+ if (!visitor.validationMessages.isEmpty()) {
+ throw new IllegalArgumentException("Validation of project failed:\n o " + MESSAGES_JOINER.join(visitor.validationMessages));
+ }
+ } finally {
+ session.close();
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "Validate project and modules keys";
+ }
+
+ private static class ValidateProjectsVisitor extends DepthTraversalTypeAwareVisitor {
+ private final DbSession session;
+ private final ComponentDao componentDao;
+ private final BatchReport.Metadata reportMetadata;
+ private final BatchReportReader batchReportReader;
+ private final boolean preventAutomaticProjectCreation;
+ private final Map<String, ComponentDto> modulesByKey;
+ private final List<String> validationMessages = new ArrayList<>();
+
+ private Component root;
+
+ public ValidateProjectsVisitor(DbSession session, ComponentDao componentDao, BatchReport.Metadata reportMetadata, BatchReportReader batchReportReader,
+ boolean preventAutomaticProjectCreation, Map<String, ComponentDto> modulesByKey) {
+ super(Component.Type.MODULE, Order.PRE_ORDER);
+ this.session = session;
+ this.componentDao = componentDao;
+ this.reportMetadata = reportMetadata;
+ this.batchReportReader = batchReportReader;
+
+ this.preventAutomaticProjectCreation = preventAutomaticProjectCreation;
+ this.modulesByKey = modulesByKey;
+ }
+
+ @Override
+ public void visitProject(Component project) {
+ this.root = project;
+ validateBranch();
+ validateBatchKey(project);
+
+ String projectKey = project.getKey();
+ ComponentDto projectDto = loadComponent(projectKey);
+ if (projectDto == null) {
+ if (preventAutomaticProjectCreation) {
+ validationMessages.add(String.format("Unable to scan non-existing project '%s'", projectKey));
+ }
+ } else if (!projectDto.projectUuid().equals(projectDto.uuid())) {
+ // Project key is already used as a module of another project
+ ComponentDto anotherProject = componentDao.selectByUuid(session, projectDto.projectUuid());
+ validationMessages.add(String.format("The project \"%s\" is already defined in SonarQube but as a module of project \"%s\". "
+ + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
+ projectKey, anotherProject.key(), anotherProject.key(), projectKey));
+ }
+ }
+
+ @Override
+ public void visitModule(Component module) {
+ String projectKey = root.getKey();
+ String moduleKey = module.getKey();
+ validateBatchKey(module);
+
+ ComponentDto moduleDto = loadComponent(moduleKey);
+ if (moduleDto == null) {
+ return;
+ }
+ if (moduleDto.projectUuid().equals(moduleDto.uuid())) {
+ // module is actually a project
+ validationMessages.add(String.format("The project \"%s\" is already defined in SonarQube but not as a module of project \"%s\". "
+ + "If you really want to stop directly analysing project \"%s\", please first delete it from SonarQube and then relaunch the analysis of project \"%s\".",
+ moduleKey, projectKey, moduleKey, projectKey));
+ } else if (!moduleDto.projectUuid().equals(root.getUuid())) {
+ ComponentDto projectModule = componentDao.selectByUuid(session, moduleDto.projectUuid());
+ validationMessages.add(String.format("Module \"%s\" is already part of project \"%s\"", moduleKey, projectModule.key()));
+ }
+ }
+
+ private void validateBatchKey(Component component) {
+ String batchKey = batchReportReader.readComponent(component.getRef()).getKey();
+ if (!ComponentKeys.isValidModuleKey(batchKey)) {
+ validationMessages.add(String.format("\"%s\" is not a valid project or module key. "
+ + "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.", batchKey));
+ }
+ }
+
+ @CheckForNull
+ private void validateBranch() {
+ if (!reportMetadata.hasBranch()) {
+ return;
+ }
+ String branch = reportMetadata.getBranch();
+ if (!ComponentKeys.isValidBranch(branch)) {
+ validationMessages.add(String.format("\"%s\" is not a valid branch name. "
+ + "Allowed characters are alphanumeric, '-', '_', '.' and '/'.", branch));
+ }
+ }
+
+ private ComponentDto loadComponent(String componentKey) {
+ ComponentDto componentDto = modulesByKey.get(componentKey);
+ if (componentDto == null) {
+ // Load component from key to be able to detect issue (try to analyze a module, etc.)
+ return componentDao.selectNullableByKey(session, componentKey);
+ }
+ return componentDto;
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java
index d64ef797efa..396c5c8d33e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java
@@ -20,6 +20,8 @@
package org.sonar.server.component.db;
+import java.util.Collections;
+import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
@@ -34,9 +36,6 @@ import org.sonar.core.persistence.DbTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.NotFoundException;
-import java.util.Collections;
-import java.util.List;
-
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -311,23 +310,6 @@ public class ComponentDaoTest {
}
@Test
- public void find_modules_by_project() {
- db.prepareDbUnit(getClass(), "multi-modules.xml");
-
- List<ComponentDto> results = sut.selectModulesByProject("org.struts:struts", session);
- assertThat(results).hasSize(1);
- assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-core");
-
- results = sut.selectModulesByProject("org.struts:struts-core", session);
- assertThat(results).hasSize(1);
- assertThat(results.get(0).getKey()).isEqualTo("org.struts:struts-data");
-
- assertThat(sut.selectModulesByProject("org.struts:struts-data", session)).isEmpty();
-
- assertThat(sut.selectModulesByProject("unknown", session)).isEmpty();
- }
-
- @Test
public void find_sub_projects_by_component_keys() {
db.prepareDbUnit(getClass(), "multi-modules.xml");
@@ -464,6 +446,16 @@ public class ComponentDaoTest {
}
@Test
+ public void select_modules_from_project() {
+ db.prepareDbUnit(getClass(), "multi-modules.xml");
+
+ List<ComponentDto> components = sut.selectModulesFromProjectKey(session, "org.struts:struts");
+ assertThat(components).hasSize(3);
+
+ assertThat(sut.selectModulesFromProjectKey(session, "UNKNOWN")).isEmpty();
+ }
+
+ @Test
public void select_views_and_sub_views() {
db.prepareDbUnit(getClass(), "shared_views.xml");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
index 7ed17968743..188773c5c41 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ComputationStepsTest.java
@@ -52,12 +52,13 @@ public class ComputationStepsTest {
mock(PopulateComponentsUuidAndKeyStep.class),
mock(PersistComponentsStep.class),
mock(IndexTestsStep.class),
- mock(QualityProfileEventsStep.class)
+ mock(QualityProfileEventsStep.class),
+ mock(ValidateProjectStep.class)
);
- assertThat(registry.orderedSteps()).hasSize(20);
+ assertThat(registry.orderedSteps()).hasSize(21);
assertThat(registry.orderedSteps().get(0)).isInstanceOf(PopulateComponentsUuidAndKeyStep.class);
- assertThat(registry.orderedSteps().get(19)).isInstanceOf(SendIssueNotificationsStep.class);
+ assertThat(registry.orderedSteps().get(20)).isInstanceOf(SendIssueNotificationsStep.class);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java
new file mode 100644
index 00000000000..8bfcf5a067a
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ValidateProjectStepTest.java
@@ -0,0 +1,299 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.computation.step;
+
+import java.io.File;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.batch.protocol.Constants;
+import org.sonar.batch.protocol.output.BatchReport;
+import org.sonar.batch.protocol.output.BatchReportReader;
+import org.sonar.batch.protocol.output.BatchReportWriter;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.computation.ComputationContext;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.ComponentTreeBuilders;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.db.DbClient;
+
+public class ValidateProjectStepTest {
+
+ private static final String PROJECT_KEY = "PROJECT_KEY";
+ private static final String MODULE_KEY = "MODULE_KEY";
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @ClassRule
+ public static DbTester dbTester = new DbTester();
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ DbClient dbClient;
+
+ DbSession dbSession;
+
+ Settings settings;
+
+ File reportDir;
+ BatchReportWriter writer;
+
+ ValidateProjectStep sut;
+
+ @Before
+ public void setUp() throws Exception {
+ dbTester.truncateTables();
+ dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), new ComponentDao());
+ dbSession = dbClient.openSession(false);
+ settings = new Settings();
+ reportDir = temp.newFolder();
+ writer = new BatchReportWriter(reportDir);
+
+ sut = new ValidateProjectStep(dbClient, settings);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ dbSession.close();
+ }
+
+ @Test
+ public void not_fail_if_provisioning_enforced_and_project_exists() throws Exception {
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .build());
+
+ settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "true");
+ dbClient.componentDao().insert(dbSession, ComponentTesting.newProjectDto("ABCD").setKey(PROJECT_KEY));
+ dbSession.commit();
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null));
+ }
+
+ @Test
+ public void fail_if_provisioning_enforced_and_project_does_not_exists() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Unable to scan non-existing project '" + PROJECT_KEY + "'");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .build());
+
+ settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "true");
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null));
+ }
+
+ @Test
+ public void fail_if_provisioning_not_enforced_and_project_does_not_exists() throws Exception {
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .build());
+
+ settings.appendProperty(CoreProperties.CORE_PREVENT_AUTOMATIC_PROJECT_CREATION, "false");
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY)), null));
+ }
+
+ @Test
+ public void not_fail_on_valid_branch() throws Exception {
+ writer.writeMetadata(BatchReport.Metadata.newBuilder()
+ .setBranch("origin/master")
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .build());
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY + ":origin/master")), null));
+ }
+
+ @Test
+ public void fail_on_invalid_branch() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Validation of project failed:\n" +
+ " o \"bran#ch\" is not a valid branch name. Allowed characters are alphanumeric, '-', '_', '.' and '/'.");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder()
+ .setBranch("bran#ch")
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .build());
+
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY + ":bran#ch")), null));
+ }
+
+ @Test
+ public void fail_on_invalid_key() throws Exception {
+ String invalidProjectKey = "Project\\Key";
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Validation of project failed:\n" +
+ " o \"Project\\Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.\n" +
+ " o \"Module$Key\" is not a valid project or module key. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(invalidProjectKey)
+ .addChildRef(2)
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(2)
+ .setType(Constants.ComponentType.MODULE)
+ .setKey("Module$Key")
+ .build());
+
+ DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", invalidProjectKey,
+ new DumbComponent(Component.Type.MODULE, 2, "BCDE", "Module$Key"));
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null, ComponentTreeBuilders.from(root), null));
+ }
+
+ @Test
+ public void fail_if_module_key_is_already_used_as_project_key() throws Exception {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Validation of project failed:\n" +
+ " o The project \"" + MODULE_KEY + "\" is already defined in SonarQube but not as a module of project \"" + PROJECT_KEY + "\". " +
+ "If you really want to stop directly analysing project \"" + MODULE_KEY + "\", please first delete it from SonarQube and then relaunch the analysis of project \""
+ + PROJECT_KEY + "\".");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .addChildRef(2)
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(2)
+ .setType(Constants.ComponentType.MODULE)
+ .setKey(MODULE_KEY)
+ .build());
+
+ ComponentDto project = ComponentTesting.newProjectDto("ABCD").setKey(MODULE_KEY);
+ dbClient.componentDao().insert(dbSession, project);
+ dbSession.commit();
+
+ DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY,
+ new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY));
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(root), null));
+ }
+
+ @Test
+ public void fail_if_module_key_already_exists_in_another_project() throws Exception {
+ String anotherProjectKey = "ANOTHER_PROJECT_KEY";
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Validation of project failed:\n" +
+ " o Module \"" + MODULE_KEY + "\" is already part of project \"" + anotherProjectKey + "\"");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .addChildRef(2)
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(2)
+ .setType(Constants.ComponentType.MODULE)
+ .setKey(MODULE_KEY)
+ .build());
+
+ ComponentDto project = ComponentTesting.newProjectDto("ABCD").setKey(PROJECT_KEY);
+ ComponentDto anotherProject = ComponentTesting.newProjectDto().setKey(anotherProjectKey);
+ dbClient.componentDao().insert(dbSession, project, anotherProject);
+ ComponentDto module = ComponentTesting.newModuleDto("BCDE", anotherProject).setKey(MODULE_KEY);
+ dbClient.componentDao().insert(dbSession, module);
+ dbSession.commit();
+
+ DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY,
+ new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY));
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(root), null));
+ }
+
+ @Test
+ public void fail_if_project_key_already_exists_as_module() throws Exception {
+ String anotherProjectKey = "ANOTHER_PROJECT_KEY";
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Validation of project failed:\n" +
+ " o The project \"" + PROJECT_KEY + "\" is already defined in SonarQube but as a module of project \"" + anotherProjectKey + "\". " +
+ "If you really want to stop directly analysing project \"" + anotherProjectKey + "\", please first delete it from SonarQube and then relaunch the analysis of project \""
+ + PROJECT_KEY + "\".");
+
+ writer.writeMetadata(BatchReport.Metadata.newBuilder().build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(1)
+ .setType(Constants.ComponentType.PROJECT)
+ .setKey(PROJECT_KEY)
+ .addChildRef(2)
+ .build());
+ writer.writeComponent(BatchReport.Component.newBuilder()
+ .setRef(2)
+ .setType(Constants.ComponentType.MODULE)
+ .setKey(MODULE_KEY)
+ .build());
+
+ ComponentDto anotherProject = ComponentTesting.newProjectDto().setKey(anotherProjectKey);
+ dbClient.componentDao().insert(dbSession, anotherProject);
+ ComponentDto module = ComponentTesting.newModuleDto("ABCD", anotherProject).setKey(PROJECT_KEY);
+ dbClient.componentDao().insert(dbSession, module);
+ dbSession.commit();
+
+ DumbComponent root = new DumbComponent(Component.Type.PROJECT, 1, "ABCD", PROJECT_KEY,
+ new DumbComponent(Component.Type.MODULE, 2, "BCDE", MODULE_KEY));
+ sut.execute(new ComputationContext(new BatchReportReader(reportDir), null, null, null,
+ ComponentTreeBuilders.from(root), null));
+ }
+
+}