]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7299 Replace Ruby WS api/projects/create by java
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 19 Jan 2017 12:19:25 +0000 (13:19 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 24 Jan 2017 17:36:49 +0000 (18:36 +0100)
18 files changed:
it/it-tests/src/test/java/it/authorisation/ProvisioningPermissionTest.java
it/it-tests/src/test/java/it/qualityGate/QualityGateTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-server/src/main/java/org/sonar/server/computation/queue/ReportSubmitter.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/CreateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
server/sonar-server/src/main/resources/org/sonar/server/project/ws/create-example.json [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/project/ws/projects-example-create.json [deleted file]
server/sonar-server/src/test/java/org/sonar/server/i18n/I18nRule.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsTest.java
sonar-db/src/test/java/org/sonar/db/favorite/FavoriteDbTester.java
sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-projects.proto
sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java

index 231edba99be35bc337d70cb2c6e1f9fd584570da..40fbaf322e87694fb416c45bbe4377d3a723f2ec 100644 (file)
@@ -27,19 +27,19 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.sonar.wsclient.SonarClient;
-import org.sonar.wsclient.base.HttpException;
-import org.sonar.wsclient.project.NewProject;
-import org.sonar.wsclient.project.Project;
+import org.sonarqube.ws.WsProjects.CreateWsResponse.Project;
+import org.sonarqube.ws.client.HttpException;
 import org.sonarqube.ws.client.permission.AddGroupWsRequest;
 import org.sonarqube.ws.client.permission.AddUserWsRequest;
 import org.sonarqube.ws.client.permission.PermissionsService;
 import org.sonarqube.ws.client.permission.RemoveGroupWsRequest;
 import org.sonarqube.ws.client.permission.RemoveUserWsRequest;
+import org.sonarqube.ws.client.project.CreateRequest;
 import util.user.UserRule;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.newUserWsClient;
 import static util.selenium.Selenese.runSelenese;
 
 public class ProvisioningPermissionTest {
@@ -117,13 +117,13 @@ public class ProvisioningPermissionTest {
     final String newKey = "new-project";
     final String newName = "New Project";
 
-    SonarClient client = orchestrator.getServer().wsClient(USER_WITH_PROVISIONING, PASSWORD);
-
-    Project created = client.projectClient().create(NewProject.create().key(newKey).name(newName));
+    Project created = newUserWsClient(orchestrator, USER_WITH_PROVISIONING, PASSWORD).projects()
+      .create(CreateRequest.builder().setKey(newKey).setName(newName).build())
+      .getProject();
 
     assertThat(created).isNotNull();
-    assertThat(created.key()).isEqualTo(newKey);
-    assertThat(created.name()).isEqualTo(newName);
+    assertThat(created.getKey()).isEqualTo(newKey);
+    assertThat(created.getName()).isEqualTo(newName);
   }
 
   /**
@@ -132,11 +132,12 @@ public class ProvisioningPermissionTest {
    */
   @Test
   public void should_not_be_allowed_on_ws_without_permission() {
-    SonarClient client = orchestrator.getServer().wsClient(USER_WITHOUT_PROVISIONING, PASSWORD);
-
     thrown.expect(HttpException.class);
-    thrown.expectMessage("401");
-    client.projectClient().create(NewProject.create().key("new-project").name("New Project"));
+    thrown.expectMessage("403");
+
+    newUserWsClient(orchestrator, USER_WITHOUT_PROVISIONING, PASSWORD).projects()
+      .create(CreateRequest.builder().setKey("new-project").setName("New Project").build())
+      .getProject();
   }
 
   private static void addUserPermission(String login, String permission) {
index c660fa891093d58fc9b01a36e37e3747787a6cf4..c055e6edf6b28e77adac03e6c9cfc4db85d6332c 100644 (file)
@@ -40,7 +40,6 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
-import org.sonar.wsclient.project.NewProject;
 import org.sonar.wsclient.qualitygate.NewCondition;
 import org.sonar.wsclient.qualitygate.QualityGate;
 import org.sonar.wsclient.qualitygate.QualityGateClient;
@@ -49,6 +48,7 @@ import org.sonarqube.ws.WsCe;
 import org.sonarqube.ws.WsMeasures.Measure;
 import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse;
 import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.PostRequest;
 import org.sonarqube.ws.client.WsClient;
 import org.sonarqube.ws.client.WsResponse;
 import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest;
@@ -71,8 +71,6 @@ public class QualityGateTest {
 
   private static long DEFAULT_QUALITY_GATE;
 
-  private long provisionedProjectId = -1L;
-
   private static final String PROJECT_KEY = "sample";
 
   @ClassRule
@@ -91,9 +89,9 @@ public class QualityGateTest {
   }
 
   @Before
-  public void cleanUp() {
+  public void before() {
     orchestrator.resetData();
-    provisionedProjectId = Long.parseLong(orchestrator.getServer().adminWsClient().projectClient().create(NewProject.create().key(PROJECT_KEY).name("Sample")).id());
+    orchestrator.getServer().provisionProject(PROJECT_KEY, "Sample");
   }
 
   @Test
@@ -190,7 +188,7 @@ public class QualityGateTest {
     qgClient().createCondition(NewCondition.create(error.id()).metricKey("ncloc").operator("GT").errorThreshold("10"));
 
     qgClient().setDefault(alert.id());
-    qgClient().selectProject(error.id(), provisionedProjectId);
+    associateQualityGateToProject(error.id(), PROJECT_KEY);
 
     try {
       SonarScanner build = SonarScanner.create(projectDir("qualitygate/xoo-sample"));
@@ -303,6 +301,14 @@ public class QualityGateTest {
     return orchestrator.getServer().adminWsClient().qualityGateClient();
   }
 
+  private static void associateQualityGateToProject(long qGateId, String projectKey) {
+    newAdminWsClient(orchestrator).wsConnector()
+      .call(new PostRequest("api/qualitygates/select")
+        .setParam("gateId", qGateId)
+        .setParam("projectKey", projectKey))
+      .failIfNotSuccessful();
+  }
+
   private static List<String> extractPosttaskPluginLogs(String taskUuid, Iterable<String> ceLogs) {
     return StreamSupport.stream(ceLogs.spliterator(), false)
       .filter(s -> s.contains("POSTASKPLUGIN: finished()"))
index 2fb921e652530b7660b1066d6c8e0c6d1c4c7223..f525fb70326e6016fa4099e553f8a679c2212772 100644 (file)
@@ -35,7 +35,6 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.System2;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentKeys;
-import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -89,7 +88,6 @@ public class ComponentService {
 
   // Used by SQ and Governance
   public ComponentDto create(DbSession session, NewComponent newComponent) {
-    userSession.checkPermission(GlobalPermissions.PROVISIONING);
     checkKeyFormat(newComponent.qualifier(), newComponent.key());
     ComponentDto rootComponent = createRootComponent(session, newComponent);
     removeDuplicatedProjects(session, rootComponent.getKey());
index 9adc1b4d766e319b51c6f3d5336a97c0e118f598..e5660d58d88b3036446964f7e4cb8cfcbbc91878 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.server.user.UserSession;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
 import static org.sonar.server.component.NewComponent.newComponentBuilder;
 import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
@@ -97,6 +98,7 @@ public class ReportSubmitter {
   }
 
   private ComponentDto createProject(DbSession dbSession, String organizationUuid, String projectKey, @Nullable String projectBranch, @Nullable String projectName) {
+    userSession.checkPermission(PROVISIONING);
     Integer userId = userSession.getUserId();
     Long projectCreatorUserId = userId == null ? null : userId.longValue();
 
@@ -113,7 +115,6 @@ public class ReportSubmitter {
       .setBranch(projectBranch)
       .setQualifier(Qualifiers.PROJECT)
       .build();
-    // "provisioning" permission is check in ComponentService
     ComponentDto project = componentService.create(dbSession, newProject);
     if (permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(dbSession, organizationUuid, project)) {
       favoriteUpdater.add(dbSession, project);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/CreateAction.java
new file mode 100644 (file)
index 0000000..6ec82ad
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.project.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.permission.PermissionTemplateService;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.WsProjects.CreateWsResponse;
+import org.sonarqube.ws.client.project.CreateRequest;
+
+import static org.sonar.api.resources.Qualifiers.PROJECT;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
+import static org.sonar.server.component.NewComponent.newComponentBuilder;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
+
+public class CreateAction implements ProjectsWsAction {
+
+  public static final String DEPRECATED_PARAM_KEY = "key";
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final ComponentService componentService;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
+  private final PermissionTemplateService permissionTemplateService;
+  private final FavoriteUpdater favoriteUpdater;
+
+  public CreateAction(DbClient dbClient, UserSession userSession, ComponentService componentService, PermissionTemplateService permissionTemplateService,
+    FavoriteUpdater favoriteUpdater, DefaultOrganizationProvider defaultOrganizationProvider) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+    this.componentService = componentService;
+    this.permissionTemplateService = permissionTemplateService;
+    this.favoriteUpdater = favoriteUpdater;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction(ACTION_CREATE)
+      .setDescription("Create a project.<br/>" +
+        "Requires 'Create Projects' permission<br/>" +
+        "Since 6.3, the response has been updated and does not contain the database ID anymore")
+      .setSince("4.0")
+      .setPost(true)
+      .setResponseExample(getClass().getResource("create-example.json"))
+      .setHandler(this);
+
+    action.createParam(PARAM_PROJECT)
+      .setDescription("Key of the project")
+      .setDeprecatedKey(DEPRECATED_PARAM_KEY)
+      .setRequired(true)
+      .setExampleValue(KEY_PROJECT_EXAMPLE_001);
+
+    action.createParam(PARAM_NAME)
+      .setDescription("Name of the project")
+      .setRequired(true)
+      .setExampleValue("SonarQube");
+
+    action.createParam(PARAM_BRANCH)
+      .setDescription("SCM Branch of the project. The key of the project will become key:branch, for instance 'SonarQube:branch-5.0'")
+      .setExampleValue("branch-5.0");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkPermission(PROVISIONING);
+    CreateRequest createRequest = toCreateRequest(request);
+    writeProtobuf(doHandle(createRequest), request, response);
+  }
+
+  private CreateWsResponse doHandle(CreateRequest request) {
+    String organizationUuid = defaultOrganizationProvider.get().getUuid();
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      ComponentDto componentDto = componentService.create(dbSession, newComponentBuilder()
+        .setOrganizationUuid(organizationUuid)
+        .setKey(request.getKey())
+        .setName(request.getName())
+        .setBranch(request.getBranch())
+        .setQualifier(PROJECT)
+        .build());
+      handlePermissionTemplate(dbSession, componentDto, organizationUuid);
+      return toCreateResponse(componentDto);
+    }
+  }
+
+  private void handlePermissionTemplate(DbSession dbSession, ComponentDto componentDto, String organizationUuid) {
+    permissionTemplateService.applyDefault(dbSession, organizationUuid, componentDto, userSession.isLoggedIn() ? userSession.getUserId().longValue() : null);
+    if (permissionTemplateService.hasDefaultTemplateWithPermissionOnProjectCreator(dbSession, organizationUuid, componentDto)) {
+      favoriteUpdater.add(dbSession, componentDto);
+      dbSession.commit();
+    }
+  }
+
+  private static CreateRequest toCreateRequest(Request request) {
+    return CreateRequest.builder()
+      .setKey(request.mandatoryParam(PARAM_PROJECT))
+      .setName(request.mandatoryParam(PARAM_NAME))
+      .setBranch(request.param(PARAM_BRANCH))
+      .build();
+  }
+
+  private static CreateWsResponse toCreateResponse(ComponentDto componentDto) {
+    return CreateWsResponse.newBuilder()
+      .setProject(CreateWsResponse.Project.newBuilder()
+        .setKey(componentDto.key())
+        .setName(componentDto.name())
+        .setQualifier(componentDto.qualifier()))
+      .build();
+  }
+
+}
index 8a06da7d277276f7ed6e20795920ae014c07a4ca..e6a42102996309816e2577dc82d428c3f1554d71 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.project.ws;
 
 import com.google.common.io.Resources;
+import java.util.Arrays;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
 
@@ -43,12 +44,7 @@ public class ProjectsWs implements WebService {
       .setDescription("Manage project existence.");
 
     defineIndexAction(controller);
-    defineCreateAction(controller);
-
-    for (ProjectsWsAction action : actions) {
-      action.define(controller);
-    }
-
+    Arrays.stream(actions).forEach(action -> action.define(controller));
     controller.done();
   }
 
@@ -95,29 +91,4 @@ public class ProjectsWs implements WebService {
     RailsHandler.addFormatParam(action);
   }
 
-  private void defineCreateAction(NewController controller) {
-    WebService.NewAction action = controller.createAction("create")
-      .setDescription("Create a project. Requires Create Projects permission")
-      .setSince("4.0")
-      .setPost(true)
-      .setHandler(RailsHandler.INSTANCE)
-      .setResponseExample(Resources.getResource(this.getClass(), "projects-example-create.json"));
-
-    action.createParam("key")
-      .setDescription("Key of the project")
-      .setRequired(true)
-      .setExampleValue(KEY_PROJECT_EXAMPLE_001);
-
-    action.createParam("name")
-      .setDescription("Name of the project")
-      .setRequired(true)
-      .setExampleValue("SonarQube");
-
-    action.createParam("branch")
-      .setDescription("SCM Branch of the project. The key of the project will become key:branch, for instance 'SonarQube:branch-5.0'")
-      .setRequired(false)
-      .setExampleValue("branch-5.0");
-
-    RailsHandler.addFormatParam(action);
-  }
 }
index 782b619bd9a72b99f527eb29c31b4c5f011f5839..e3e5138bf554a1408b3face3bbe9f5d0e5d682df 100644 (file)
@@ -26,6 +26,7 @@ public class ProjectsWsModule extends Module {
   protected void configureModule() {
     add(
       ProjectsWs.class,
+      CreateAction.class,
       BulkDeleteAction.class,
       DeleteAction.class,
       GhostsAction.class,
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/create-example.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/create-example.json
new file mode 100644 (file)
index 0000000..077da79
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "project": {
+    "key": "project-key",
+    "name": "project-name",
+    "qualifier": "TRK"
+  }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/projects-example-create.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/projects-example-create.json
deleted file mode 100644 (file)
index 043c4bb..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "id": "30057",
-  "k": "org.jenkins-ci.plugins:sonar",
-  "nm": "Jenkins Sonar Plugin",
-  "sc": "PRJ",
-  "qu": "TRK"
-}
-
index 5d2218620e2dcfa730040333e3ab869f4bc0f1d2..eb6c4c01fbbde67ebe039ec1fd7ac0fff2696cd9 100644 (file)
@@ -26,9 +26,12 @@ import java.util.Locale;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 import org.sonar.api.i18n.I18n;
 
-public class I18nRule implements I18n {
+public class I18nRule implements TestRule, I18n {
   private final Map<String, String> messages = new HashMap<>();
 
   public I18nRule put(String key, String value) {
@@ -36,6 +39,20 @@ public class I18nRule implements I18n {
     return this;
   }
 
+  @Override
+  public Statement apply(final Statement statement, Description description) {
+    return new Statement() {
+      @Override
+      public void evaluate() throws Throwable {
+        try {
+          statement.evaluate();
+        } finally {
+          messages.clear();
+        }
+      }
+    };
+  }
+
   public void setProjectPermissions() {
     put("projects_role.admin", "Administer");
     put("projects_role.admin.desc", "Ability to access project settings and perform administration tasks. " +
@@ -101,4 +118,5 @@ public class I18nRule implements I18n {
   public String formatInteger(Locale locale, Integer value) {
     return String.valueOf(value);
   }
+
 }
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..541ad2afdb35ab9232e97e49aff3baeea93ea4db 100644 (file)
@@ -0,0 +1,322 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.project.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.MapSettings;
+import org.sonar.api.config.Settings;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.permission.template.PermissionTemplateDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.component.ComponentService;
+import org.sonar.server.component.index.ComponentIndexDefinition;
+import org.sonar.server.component.index.ComponentIndexer;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.favorite.FavoriteUpdater;
+import org.sonar.server.i18n.I18nRule;
+import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
+import org.sonar.server.measure.index.ProjectMeasuresIndexer;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.permission.PermissionTemplateService;
+import org.sonar.server.permission.index.PermissionIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsProjects.CreateWsResponse;
+import org.sonarqube.ws.client.project.CreateRequest;
+
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
+import static org.sonar.core.permission.GlobalPermissions.QUALITY_GATE_ADMIN;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.server.component.index.ComponentIndexDefinition.INDEX_COMPONENTS;
+import static org.sonar.server.component.index.ComponentIndexDefinition.TYPE_COMPONENT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.INDEX_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURE;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.client.WsRequest.Method.POST;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
+
+public class CreateActionTest {
+
+  private static final String DEFAULT_PROJECT_KEY = "project-key";
+  private static final String DEFAULT_PROJECT_NAME = "project-name";
+
+  private System2 system2 = System2.INSTANCE;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  @Rule
+  public EsTester es = new EsTester(new ComponentIndexDefinition(new MapSettings()), new ProjectMeasuresIndexDefinition(new MapSettings()));
+
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+
+  @Rule
+  public I18nRule i18n = new I18nRule().put("qualifier.TRK", "Project");
+
+  private Settings settings = new MapSettings();
+
+  private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+
+  private PermissionTemplateDto permissionTemplateDto;
+
+  private WsActionTester ws = new WsActionTester(
+    new CreateAction(
+      db.getDbClient(), userSession,
+      new ComponentService(db.getDbClient(), i18n, userSession, system2,
+        new ProjectMeasuresIndexer(system2, db.getDbClient(), es.client()),
+        new ComponentIndexer(db.getDbClient(), es.client())),
+      new PermissionTemplateService(db.getDbClient(), settings, new PermissionIndexer(db.getDbClient(), es.client()), userSession),
+      new FavoriteUpdater(db.getDbClient(), userSession),
+      defaultOrganizationProvider));
+
+  @Before
+  public void setUp() throws Exception {
+    permissionTemplateDto = db.permissionTemplates().insertTemplate();
+    setTemplateAsDefault(permissionTemplateDto);
+  }
+
+  @Test
+  public void create_project() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+
+    CreateWsResponse response = call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    assertThat(response.getProject().getKey()).isEqualTo(DEFAULT_PROJECT_KEY);
+    assertThat(response.getProject().getName()).isEqualTo(DEFAULT_PROJECT_NAME);
+    assertThat(response.getProject().getQualifier()).isEqualTo("TRK");
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(project.getKey()).isEqualTo(DEFAULT_PROJECT_KEY);
+    assertThat(project.name()).isEqualTo(DEFAULT_PROJECT_NAME);
+    assertThat(project.qualifier()).isEqualTo("TRK");
+  }
+
+  @Test
+  public void create_project_with_branch() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+
+    CreateWsResponse response = call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .setBranch("origin/master")
+      .build());
+
+    assertThat(response.getProject().getKey()).isEqualTo("project-key:origin/master");
+  }
+
+  @Test
+  public void verify_permission_template_is_applied() throws Exception {
+    UserDto userDto = db.users().insertUser();
+    userSession.login(userDto).setGlobalPermissions(PROVISIONING);
+    db.permissionTemplates().addUserToTemplate(permissionTemplateDto.getId(), userDto.getId(), USER);
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(db.users().selectProjectPermissionsOfUser(userDto, project)).containsOnly(USER);
+  }
+
+  @Test
+  public void add_project_to_favorite_when_logged() throws Exception {
+    UserDto userDto = db.users().insertUser();
+    userSession.login(userDto).setGlobalPermissions(PROVISIONING);
+    db.permissionTemplates().addProjectCreatorToTemplate(permissionTemplateDto.getId(), USER);
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(db.favorites().hasFavorite(project, userDto.getId())).isTrue();
+  }
+
+  @Test
+  public void does_not_add_project_to_favorite_when_not_logged() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+    db.permissionTemplates().addProjectCreatorToTemplate(permissionTemplateDto.getId(), USER);
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(db.favorites().hasNoFavorite(project)).isTrue();
+  }
+
+  @Test
+  public void does_not_add_project_to_favorite_when_project_create_has_no_permission_on_template() throws Exception {
+    UserDto userDto = db.users().insertUser();
+    userSession.login(userDto).setGlobalPermissions(PROVISIONING);
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(db.favorites().hasNoFavorite(project)).isTrue();
+  }
+
+  @Test
+  public void verify_project_exists_in_es_indexes() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+
+    ComponentDto project = db.getDbClient().componentDao().selectOrFailByKey(db.getSession(), DEFAULT_PROJECT_KEY);
+    assertThat(es.getIds(INDEX_COMPONENTS, TYPE_COMPONENT)).containsOnly(project.uuid());
+    assertThat(es.getIds(INDEX_PROJECT_MEASURES, TYPE_PROJECT_MEASURE)).containsOnly(project.uuid());
+  }
+
+  @Test
+  public void create_project_with_deprecated_parameter() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+
+    ws.newRequest()
+      .setMethod(POST.name())
+      .setParam("key", DEFAULT_PROJECT_KEY)
+      .setParam(PARAM_NAME, DEFAULT_PROJECT_NAME)
+      .execute();
+
+    assertThat(db.getDbClient().componentDao().selectByKey(db.getSession(), DEFAULT_PROJECT_KEY).isPresent()).isTrue();
+  }
+
+  @Test
+  public void fail_when_project_already_exists() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+    db.components().insertComponent(ComponentTesting.newProjectDto(db.getDefaultOrganization()).setKey(DEFAULT_PROJECT_KEY));
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Could not create Project, key already exists: project-key");
+
+    call(CreateRequest.builder()
+      .setKey(DEFAULT_PROJECT_KEY)
+      .setName(DEFAULT_PROJECT_NAME)
+      .build());
+  }
+
+  @Test
+  public void fail_when_missing_project_parameter() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'project' parameter is missing");
+
+    call(CreateRequest.builder().setName(DEFAULT_PROJECT_NAME).build());
+  }
+
+  @Test
+  public void fail_when_missing_name_parameter() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("The 'name' parameter is missing");
+
+    call(CreateRequest.builder().setKey(DEFAULT_PROJECT_KEY).build());
+  }
+
+  @Test
+  public void fail_when_key_has_bad_format() throws Exception {
+    userSession.setGlobalPermissions(PROVISIONING);
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Malformed key for Project: 1234");
+
+    call(CreateRequest.builder().setKey("1234").setName(DEFAULT_PROJECT_NAME).build());
+  }
+
+  @Test
+  public void fail_when_missing_create_project_permission() throws Exception {
+    userSession.setGlobalPermissions(QUALITY_GATE_ADMIN);
+    expectedException.expect(ForbiddenException.class);
+
+    call(CreateRequest.builder().setKey(DEFAULT_PROJECT_KEY).setName(DEFAULT_PROJECT_NAME).build());
+  }
+
+  @Test
+  public void test_example() {
+    userSession.setGlobalPermissions(PROVISIONING);
+
+    String result = ws.newRequest()
+      .setParam("key", DEFAULT_PROJECT_KEY)
+      .setParam("name", DEFAULT_PROJECT_NAME)
+      .execute().getInput();
+
+    assertJson(result).isSimilarTo(getClass().getResource("create-example.json"));
+  }
+
+  @Test
+  public void definition() {
+    WebService.Action definition = ws.getDef();
+
+    Assertions.assertThat(definition.key()).isEqualTo("create");
+    Assertions.assertThat(definition.since()).isEqualTo("4.0");
+    Assertions.assertThat(definition.isInternal()).isFalse();
+    Assertions.assertThat(definition.responseExampleAsString()).isNotEmpty();
+    Assertions.assertThat(definition.params()).hasSize(3);
+  }
+
+  private CreateWsResponse call(CreateRequest request) {
+    TestRequest httpRequest = ws.newRequest()
+      .setMethod(POST.name())
+      .setMediaType(MediaTypes.PROTOBUF);
+    setNullable(request.getKey(), e -> httpRequest.setParam("project", e));
+    setNullable(request.getName(), e -> httpRequest.setParam("name", e));
+    setNullable(request.getBranch(), e -> httpRequest.setParam("branch", e));
+    try {
+      return CreateWsResponse.parseFrom(httpRequest.execute().getInputStream());
+    } catch (IOException e) {
+      throw Throwables.propagate(e);
+    }
+  }
+
+  private void setTemplateAsDefault(PermissionTemplateDto permissionTemplateDto) {
+    settings.appendProperty("sonar.permission.template.default", permissionTemplateDto.getUuid());
+  }
+
+}
index f2cbc94baf4307f96a0db275d4e7e75be1e7d8bc..c2a2dab4b7473be7b8d5104c2272458dd20512de 100644 (file)
@@ -29,6 +29,6 @@ public class ProjectsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ProjectsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(2 + 7);
+    assertThat(container.size()).isEqualTo(2 + 8);
   }
 }
index fe2701d163bf173d5a3b7b5956e12d95cd0b00f4..214e9f176e46afee9471a0e9b95ae8febbf42190 100644 (file)
@@ -41,8 +41,7 @@ public class ProjectsWsTest {
     ws = new WsTester(new ProjectsWs(
       new BulkDeleteAction(mock(ComponentCleanerService.class), mock(DbClient.class), mock(UserSession.class)),
       new GhostsAction(mock(DbClient.class), mock(UserSession.class)),
-      new ProvisionedAction(mock(DbClient.class), mock(UserSession.class))
-    ));
+      new ProvisionedAction(mock(DbClient.class), mock(UserSession.class))));
     controller = ws.controller("api/projects");
   }
 
@@ -51,7 +50,7 @@ public class ProjectsWsTest {
     assertThat(controller).isNotNull();
     assertThat(controller.description()).isNotEmpty();
     assertThat(controller.since()).isEqualTo("2.10");
-    assertThat(controller.actions()).hasSize(5);
+    assertThat(controller.actions()).hasSize(4);
   }
 
   @Test
@@ -63,12 +62,4 @@ public class ProjectsWsTest {
     assertThat(action.params()).hasSize(8);
   }
 
-  @Test
-  public void define_create_action() {
-    WebService.Action action = controller.action("create");
-    assertThat(action).isNotNull();
-    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
-    assertThat(action.responseExampleAsString()).isNotEmpty();
-    assertThat(action.params()).hasSize(4);
-  }
 }
index 452bfbac197330dea2406cc4a42cbfe252015bdf..f3f45156d37ae062234933dca8e7a40d263c39e5 100644 (file)
@@ -31,12 +31,10 @@ import org.sonar.db.property.PropertyQuery;
 public class FavoriteDbTester {
   private static final String PROP_FAVORITE_KEY = "favourite";
 
-  private final DbTester db;
   private final DbClient dbClient;
   private final DbSession dbSession;
 
   public FavoriteDbTester(DbTester db) {
-    this.db = db;
     this.dbClient = db.getDbClient();
     this.dbSession = db.getSession();
   }
@@ -58,4 +56,12 @@ public class FavoriteDbTester {
 
     return !result.isEmpty();
   }
+
+  public boolean hasNoFavorite(ComponentDto componentDto) {
+    List<PropertyDto> result = dbClient.propertiesDao().selectByQuery(PropertyQuery.builder()
+      .setKey(PROP_FAVORITE_KEY)
+      .setComponentId(componentDto.getId())
+      .build(), dbSession);
+    return result.isEmpty();
+  }
 }
index 2eb5ae1af0b42065498eaa76601ba20a39f85d1a..37f0db639a748f433fc6242965e448c707313432 100644 (file)
 
 package org.sonarqube.ws.client.project;
 
+import org.sonarqube.ws.WsProjects.CreateWsResponse;
 import org.sonarqube.ws.client.BaseService;
 import org.sonarqube.ws.client.PostRequest;
 import org.sonarqube.ws.client.WsConnector;
 
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME;
+import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
+
 /**
  * Maps web service {@code api/projects}.
  * @since 5.5
@@ -31,7 +38,7 @@ import org.sonarqube.ws.client.WsConnector;
 public class ProjectsService extends BaseService {
 
   public ProjectsService(WsConnector wsConnector) {
-    super(wsConnector, "api/projects");
+    super(wsConnector, CONTROLLER);
   }
 
   /**
@@ -39,12 +46,12 @@ public class ProjectsService extends BaseService {
    *
    * @throws org.sonarqube.ws.client.HttpException if HTTP status code is not 2xx.
    */
-  public void create(CreateRequest project) {
-    PostRequest request = new PostRequest(path("create"))
-      .setParam("key", project.getKey())
-      .setParam("name", project.getName())
-      .setParam("branch", project.getBranch());
-    call(request);
+  public CreateWsResponse create(CreateRequest project) {
+    PostRequest request = new PostRequest(path(ACTION_CREATE))
+      .setParam(PARAM_PROJECT, project.getKey())
+      .setParam(PARAM_NAME, project.getName())
+      .setParam(PARAM_BRANCH, project.getBranch());
+    return call(request, CreateWsResponse.parser());
   }
 
   /**
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java
new file mode 100644 (file)
index 0000000..f6ca6e6
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonarqube.ws.client.project;
+
+public class ProjectsWsParameters {
+
+  public static final String CONTROLLER = "api/projects";
+
+  public static final String ACTION_CREATE = "create";
+
+  public static final String PARAM_PROJECT = "project";
+  public static final String PARAM_NAME = "name";
+  public static final String PARAM_BRANCH = "branch";
+
+  private ProjectsWsParameters() {
+    // static utils only
+  }
+}
index 6df23d6edfc4ea2cca0526eb98ea5a13e590409b..64fb78379ac1e03204ab65a5ada5c63f6fba3e0f 100644 (file)
@@ -46,3 +46,14 @@ message SearchMyProjectsWsResponse {
   optional sonarqube.ws.commons.Paging paging = 1;
   repeated Project projects = 2;
 }
+
+message CreateWsResponse {
+  optional Project project = 1;
+
+  message Project {
+    optional string key = 1;
+    optional string name = 2;
+    optional string qualifier = 3;
+  }
+}
+
index ccb720b00fd49fdd2220308a85178ff3c55297ed..94fc4c2b91e0c6a1a37cb2721c4c00dedbb038d0 100644 (file)
@@ -21,6 +21,7 @@ package org.sonarqube.ws.client.project;
 
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonarqube.ws.WsProjects;
 import org.sonarqube.ws.client.ServiceTester;
 import org.sonarqube.ws.client.WsConnector;
 
@@ -42,9 +43,10 @@ public class ProjectsServiceTest {
       .setName("Project Name")
       .build());
 
+    assertThat(serviceTester.getPostParser()).isSameAs(WsProjects.CreateWsResponse.parser());
     assertThat(serviceTester.getPostRequest().getPath()).isEqualTo("api/projects/create");
     assertThat(serviceTester.getPostRequest().getParams()).containsOnly(
-      entry("key", "project_key"),
+      entry("project", "project_key"),
       entry("name", "Project Name"));
   }
 
@@ -58,7 +60,7 @@ public class ProjectsServiceTest {
 
     assertThat(serviceTester.getPostRequest().getPath()).isEqualTo("api/projects/create");
     assertThat(serviceTester.getPostRequest().getParams()).containsOnly(
-      entry("key", "project_key"),
+      entry("project", "project_key"),
       entry("name", "Project Name"),
       entry("branch", "the_branch"));
   }