diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-12-06 18:34:44 +0100 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2016-12-07 17:23:30 +0100 |
commit | fbb33db724f16444ac882a4ffe88d15e8ff39837 (patch) | |
tree | 5a20bb22ec851a140d3a1ba5764c2055170e5c9e /server | |
parent | 2f2b6fd1d3a3674d2864c1927e902d043f6e44ca (diff) | |
download | sonarqube-fbb33db724f16444ac882a4ffe88d15e8ff39837.tar.gz sonarqube-fbb33db724f16444ac882a4ffe88d15e8ff39837.zip |
SONAR-8464 Create WS api/project_analyses/create_event
Diffstat (limited to 'server')
11 files changed, 776 insertions, 3 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java index dbe62325ffa..e55526c4218 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java @@ -121,9 +121,9 @@ public class ComponentTreeAction implements MeasuresWsAction { .setDescription(format("Navigate through components based on the chosen strategy with specified measures. The %s or the %s parameter must be provided.<br>" + "Requires one of the following permissions:" + "<ul>" + - "<li>'Administer System'</li>" + - "<li>'Administer' rights on the specified project</li>" + - "<li>'Browse' on the specified project</li>" + + " <li>'Administer System'</li>" + + " <li>'Administer' rights on the specified project</li>" + + " <li>'Browse' on the specified project</li>" + "</ul>" + "When limiting search with the %s parameter, directories are not returned.", PARAM_BASE_COMPONENT_ID, PARAM_BASE_COMPONENT_KEY, Param.TEXT_QUERY)) diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 5613a5ff655..3255dc6f23d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -148,6 +148,7 @@ import org.sonar.server.plugins.ws.PluginsWs; import org.sonar.server.plugins.ws.UninstallAction; import org.sonar.server.plugins.ws.UpdatesAction; import org.sonar.server.project.ws.ProjectsWsModule; +import org.sonar.server.projectanalysis.ProjectAnalysisModule; import org.sonar.server.projectlink.ws.ProjectLinksModule; import org.sonar.server.property.InternalPropertiesImpl; import org.sonar.server.qualitygate.QualityGateModule; @@ -490,6 +491,9 @@ public class PlatformLevel4 extends PlatformLevel { // Project Links ProjectLinksModule.class, + // Project Analyses + ProjectAnalysisModule.class, + // System ServerLogging.class, RestartAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java new file mode 100644 index 00000000000..0c98286be87 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java @@ -0,0 +1,37 @@ +/* + * 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.projectanalysis; + +import org.sonar.core.platform.Module; +import org.sonar.server.projectanalysis.ws.CreateEventAction; +import org.sonar.server.projectanalysis.ws.ProjectAnalysesWs; + +public class ProjectAnalysisModule extends Module { + + @Override + protected void configureModule() { + add( + ProjectAnalysesWs.class, + // actions + CreateEventAction.class); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/package-info.java new file mode 100644 index 00000000000..950feac72ca --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.projectanalysis; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/CreateEventAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/CreateEventAction.java new file mode 100644 index 00000000000..d2ea1f89d6a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/CreateEventAction.java @@ -0,0 +1,214 @@ +/* + * 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.projectanalysis.ws; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.event.EventDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.ProjectAnalyses.CreateEventResponse; +import org.sonarqube.ws.ProjectAnalyses.Event; +import org.sonarqube.ws.client.projectanalysis.CreateEventRequest; +import org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.lang.String.format; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.OTHER; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.VERSION; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.fromLabel; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_ANALYSIS; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_CATEGORY; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_DESCRIPTION; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_NAME; + +public class CreateEventAction implements ProjectAnalysesWsAction { + private final DbClient dbClient; + private final UuidFactory uuidFactory; + private final System2 system; + private final UserSession userSession; + + public CreateEventAction(DbClient dbClient, UuidFactory uuidFactory, System2 system, UserSession userSession) { + this.dbClient = dbClient; + this.uuidFactory = uuidFactory; + this.system = system; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("create_event") + .setDescription("Add an event to a project analysis.<br>" + + "Requires one of the following permissions:" + + "<ul>" + + " <li>'Administer System'</li>" + + " <li>'Administer' rights on the specified project</li>" + + "</ul>") + .setSince("6.3") + .setPost(true) + .setResponseExample(getClass().getResource("create_event-example.json")) + .setHandler(this); + + action.createParam(PARAM_ANALYSIS) + .setDescription("Analysis key") + .setExampleValue(Uuids.UUID_EXAMPLE_01) + .setRequired(true); + + action.createParam(PARAM_CATEGORY) + .setDescription("Category") + .setDefaultValue(OTHER) + .setPossibleValues(VERSION, OTHER); + + action.createParam(PARAM_NAME) + .setDescription("Name") + .setExampleValue("5.6") + .setRequired(true); + + action.createParam(PARAM_DESCRIPTION) + .setDescription("Description") + .setExampleValue("Version released"); + } + + @Override + public void handle(Request httpRequest, Response httpResponse) throws Exception { + CreateEventRequest request = toAddEventRequest(httpRequest); + CreateEventResponse response = doHandle(request); + + writeProtobuf(response, httpRequest, httpResponse); + } + + private CreateEventResponse doHandle(CreateEventRequest request) { + try (DbSession dbSession = dbClient.openSession(false)) { + SnapshotDto analysis = getAnalysis(dbSession, request); + checkPermissions(analysis); + checkExistingDbEvents(dbSession, request, analysis); + EventDto dbEvent = insertDbEvent(dbSession, request, analysis); + return toCreateEventResponse(dbEvent); + } + } + + private void checkPermissions(SnapshotDto analysis) { + userSession.checkComponentUuidPermission(UserRole.ADMIN, analysis.getComponentUuid()); + } + + private EventDto insertDbEvent(DbSession dbSession, CreateEventRequest request, SnapshotDto analysis) { + EventDto dbEvent = dbClient.eventDao().insert(dbSession, toDbEvent(request, analysis)); + if (VERSION.equals(request.getCategory())) { + dbClient.snapshotDao().updateVersion(dbSession, analysis.getUuid(), request.getName()); + } + dbSession.commit(); + return dbEvent; + } + + private SnapshotDto getAnalysis(DbSession dbSession, CreateEventRequest request) { + SnapshotDto analysis = dbClient.snapshotDao().selectByUuid(dbSession, request.getAnalysis()) + .orElseThrow(() -> new NotFoundException(format("Analysis '%s' is not found", request.getAnalysis()))); + ComponentDto project = dbClient.componentDao().selectByUuid(dbSession, analysis.getComponentUuid()).orNull(); + checkState(project != null, "Project of analysis '%s' is not found", analysis.getUuid()); + checkArgument(Qualifiers.PROJECT.equals(project.qualifier()) && Scopes.PROJECT.equals(project.scope()), + "An event must be created on a project"); + return analysis; + } + + private static CreateEventRequest toAddEventRequest(Request request) { + return CreateEventRequest.builder() + .setAnalysis(request.mandatoryParam(PARAM_ANALYSIS)) + .setName(request.mandatoryParam(PARAM_NAME)) + .setCategory(request.mandatoryParamAsEnum(PARAM_CATEGORY, Category.class)) + .setDescription(request.param(PARAM_DESCRIPTION)) + .build(); + } + + private EventDto toDbEvent(CreateEventRequest request, SnapshotDto analysis) { + return new EventDto() + .setUuid(uuidFactory.create()) + .setAnalysisUuid(analysis.getUuid()) + .setComponentUuid(analysis.getComponentUuid()) + .setCategory(request.getCategory().getLabel()) + .setName(request.getName()) + .setDescription(request.getDescription()) + .setCreatedAt(system.now()) + .setDate(analysis.getCreatedAt()); + } + + private static CreateEventResponse toCreateEventResponse(EventDto dbEvent) { + Event.Builder wsEvent = Event.newBuilder() + .setKey(dbEvent.getUuid()) + .setCategory(fromLabel(dbEvent.getCategory()).name()) + .setAnalysis(dbEvent.getAnalysisUuid()) + .setName(dbEvent.getName()); + setNullable(dbEvent.getDescription(), wsEvent::setDescription); + + return CreateEventResponse.newBuilder().setEvent(wsEvent).build(); + } + + private void checkExistingDbEvents(DbSession dbSession, CreateEventRequest request, SnapshotDto analysis) { + List<EventDto> dbEvents = dbClient.eventDao().selectByAnalysisUuid(dbSession, analysis.getUuid()); + Predicate<EventDto> similarEventExisting = filterSimilarEvents(request); + dbEvents.stream() + .filter(similarEventExisting) + .findAny() + .ifPresent(throwException(request)); + } + + private static Predicate<EventDto> filterSimilarEvents(CreateEventRequest request) { + switch (request.getCategory()) { + case VERSION: + return dbEvent -> VERSION.getLabel().equals(dbEvent.getCategory()); + case OTHER: + return dbEvent -> OTHER.getLabel().equals(dbEvent.getCategory()) && request.getName().equals(dbEvent.getName()); + default: + throw new IllegalStateException("Event category not handled: " + request.getCategory()); + } + } + + private static Consumer<EventDto> throwException(CreateEventRequest request) { + switch (request.getCategory()) { + case VERSION: + return dbEvent -> { + throw new IllegalArgumentException(format("A version event already exists on analysis '%s'", request.getAnalysis())); + }; + case OTHER: + return dbEvent -> { + throw new IllegalArgumentException(format("An 'other' event with the same name already exists on analysis '%s'", request.getAnalysis())); + }; + default: + throw new IllegalStateException("Event category not handled: " + request.getCategory()); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWs.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWs.java new file mode 100644 index 00000000000..2e878dbda73 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWs.java @@ -0,0 +1,45 @@ +/* + * 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.projectanalysis.ws; + +import org.sonar.api.server.ws.WebService; + +public class ProjectAnalysesWs implements WebService { + + private final ProjectAnalysesWsAction[] actions; + + public ProjectAnalysesWs(ProjectAnalysesWsAction... actions) { + this.actions = actions; + } + + @Override + public void define(Context context) { + NewController controller = context.createController("api/project_analyses") + .setDescription("Manage project analyses.") + .setSince("6.3"); + + for (ProjectAnalysesWsAction action : actions) { + action.define(controller); + } + + controller.done(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsAction.java new file mode 100644 index 00000000000..4b14578ff6d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsAction.java @@ -0,0 +1,27 @@ +/* + * 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.projectanalysis.ws; + +import org.sonar.server.ws.WsAction; + +public interface ProjectAnalysesWsAction extends WsAction { + // Marker interface +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/package-info.java new file mode 100644 index 00000000000..c4063ae5311 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ + +@ParametersAreNonnullByDefault +package org.sonar.server.projectanalysis.ws; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/create_event-example.json b/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/create_event-example.json new file mode 100644 index 00000000000..14e486d5b60 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/create_event-example.json @@ -0,0 +1,9 @@ +{ + "event": { + "analysis": "A2", + "key": "E1", + "category": "OTHER", + "name": "My Custom Event", + "description": "event description" + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java new file mode 100644 index 00000000000..40d45b0a602 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java @@ -0,0 +1,36 @@ +/* + * 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.projectanalysis; + +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectAnalysisModuleTest { + + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + new ProjectAnalysisModule().configure(container); + assertThat(container.size()).isEqualTo(2 + 2); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/CreateEventActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/CreateEventActionTest.java new file mode 100644 index 00000000000..1074e51306e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/CreateEventActionTest.java @@ -0,0 +1,351 @@ +/* + * 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.projectanalysis.ws; + +import com.google.common.base.Throwables; +import java.io.IOException; +import java.util.List; +import java.util.Optional; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.component.SnapshotTesting; +import org.sonar.db.event.EventDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +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.ProjectAnalyses; +import org.sonarqube.ws.ProjectAnalyses.CreateEventResponse; +import org.sonarqube.ws.client.projectanalysis.CreateEventRequest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.component.ComponentTesting.newView; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; +import static org.sonar.db.component.SnapshotTesting.newSnapshot; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.client.WsRequest.Method.POST; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.OTHER; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.VERSION; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_ANALYSIS; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_CATEGORY; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_DESCRIPTION; +import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_NAME; + +public class CreateEventActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + private DbClient dbClient = db.getDbClient(); + private DbSession dbSession = db.getSession(); + + private UuidFactory uuidFactory = UuidFactoryFast.getInstance(); + private System2 system = mock(System2.class); + + private WsActionTester ws = new WsActionTester(new CreateEventAction(dbClient, uuidFactory, system, userSession)); + + @Before + public void setUp() { + when(system.now()).thenReturn(42L); + } + + @Test + public void json_example() { + ComponentDto project = db.components().insertProject(); + SnapshotDto analysis = dbClient.snapshotDao().insert(dbSession, SnapshotTesting.newAnalysis(project).setUuid("A2")); + db.commit(); + uuidFactory = mock(UuidFactory.class); + when(uuidFactory.create()).thenReturn("E1"); + ws = new WsActionTester(new CreateEventAction(dbClient, uuidFactory, system, userSession)); + + String result = ws.newRequest() + .setParam(PARAM_ANALYSIS, analysis.getUuid()) + .setParam(PARAM_CATEGORY, OTHER.name()) + .setParam(PARAM_NAME, "My Custom Event") + .setParam(PARAM_DESCRIPTION, "event description") + .execute().getInput(); + + assertJson(result).isSimilarTo(getClass().getResource("create_event-example.json")); + } + + @Test + public void create_event_in_db() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3") + .setDescription("LTS version"); + when(system.now()).thenReturn(123_456_789L); + + CreateEventResponse result = call(request); + + List<EventDto> dbEvents = dbClient.eventDao().selectByComponentUuid(dbSession, analysis.getComponentUuid()); + assertThat(dbEvents).hasSize(1); + EventDto dbEvent = dbEvents.get(0); + assertThat(dbEvent.getName()).isEqualTo("5.6.3"); + assertThat(dbEvent.getCategory()).isEqualTo(VERSION.getLabel()); + assertThat(dbEvent.getDescription()).isEqualTo("LTS version"); + assertThat(dbEvent.getAnalysisUuid()).isEqualTo(analysis.getUuid()); + assertThat(dbEvent.getComponentUuid()).isEqualTo(analysis.getComponentUuid()); + assertThat(dbEvent.getUuid()).isEqualTo(result.getEvent().getKey()); + assertThat(dbEvent.getCreatedAt()).isEqualTo(123_456_789L); + assertThat(dbEvent.getDate()).isEqualTo(analysis.getCreatedAt()); + } + + @Test + public void create_event_as_project_admin() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto("P1")); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3") + .setDescription("LTS version"); + userSession.anonymous().addProjectUuidPermissions(UserRole.ADMIN, "P1"); + + CreateEventResponse result = call(request); + + assertThat(result.getEvent().getKey()).isNotEmpty(); + } + + @Test + public void create_version_event() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3") + .setDescription("LTS version"); + + call(request); + + Optional<SnapshotDto> newAnalysis = dbClient.snapshotDao().selectByUuid(dbSession, analysis.getUuid()); + assertThat(newAnalysis.get().getVersion()).isEqualTo("5.6.3"); + } + + @Test + public void create_other_event_with_ws_response() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setName("Project Import") + .setDescription("Import from another instance"); + + CreateEventResponse result = call(request); + + SnapshotDto newAnalysis = dbClient.snapshotDao().selectByUuid(dbSession, analysis.getUuid()).get(); + assertThat(analysis.getVersion()).isEqualTo(newAnalysis.getVersion()); + ProjectAnalyses.Event event = result.getEvent(); + assertThat(event.getKey()).isNotEmpty(); + assertThat(event.getCategory()).isEqualTo(OTHER.name()); + assertThat(event.getName()).isEqualTo("Project Import"); + assertThat(event.getDescription()).isEqualTo("Import from another instance"); + assertThat(event.getAnalysis()).isEqualTo(analysis.getUuid()); + } + + @Test + public void create_event_without_description() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(OTHER) + .setName("Project Import"); + + CreateEventResponse result = call(request); + + ProjectAnalyses.Event event = result.getEvent(); + assertThat(event.getKey()).isNotEmpty(); + assertThat(event.hasDescription()).isFalse(); + } + + @Test + public void create_2_version_events_on_same_project() { + ComponentDto project = newProjectDto(); + SnapshotDto firstAnalysis = db.components().insertProjectAndSnapshot(project); + CreateEventRequest.Builder firstRequest = CreateEventRequest.builder() + .setAnalysis(firstAnalysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3"); + SnapshotDto secondAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(project)); + db.commit(); + CreateEventRequest.Builder secondRequest = CreateEventRequest.builder() + .setAnalysis(secondAnalysis.getUuid()) + .setCategory(VERSION) + .setName("6.3"); + + call(firstRequest); + call(secondRequest); + + List<EventDto> events = dbClient.eventDao().selectByComponentUuid(dbSession, project.uuid()); + assertThat(events).hasSize(2); + } + + @Test + public void fail_if_analysis_is_not_found() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Analysis 'A42' is not found"); + + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis("A42") + .setCategory(OTHER) + .setName("Project Import"); + + call(request); + } + + @Test + public void fail_if_2_version_events_on_the_same_analysis() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3"); + call(request); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("A version event already exists on analysis '" + analysis.getUuid() + "'"); + + call(request.setName("6.3")); + } + + @Test + public void fail_if_2_other_events_on_same_analysis_with_same_name() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(OTHER) + .setName("Project Import"); + call(request); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("An 'other' event with the same name already exists on analysis '" + analysis.getUuid() + "'"); + + call(request.setName("Project Import")); + } + + @Test + public void fail_if_category_other_than_authorized() { + expectedException.expect(IllegalArgumentException.class); + + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto()); + ws.newRequest() + .setParam(PARAM_ANALYSIS, analysis.getUuid()) + .setParam(PARAM_NAME, "Project Import") + .setParam(PARAM_CATEGORY, "QP") + .execute(); + } + + @Test + public void fail_if_create_on_other_than_a_project() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("An event must be created on a project"); + + SnapshotDto analysis = db.components().insertViewAndSnapshot(newView()); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3"); + + call(request); + } + + @Test + public void fail_if_project_is_not_found() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Project of analysis 'A1' is not found"); + + SnapshotDto analysis = dbClient.snapshotDao().insert(dbSession, newSnapshot().setUuid("A1")); + db.commit(); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3"); + + call(request); + } + + @Test + public void fail_if_insufficient_permissions() { + SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto("P1")); + CreateEventRequest.Builder request = CreateEventRequest.builder() + .setAnalysis(analysis.getUuid()) + .setCategory(VERSION) + .setName("5.6.3") + .setDescription("LTS version"); + userSession.anonymous(); + + expectedException.expect(ForbiddenException.class); + + call(request); + + } + + @Test + public void ws_parameters() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.isPost()).isTrue(); + assertThat(definition.key()).isEqualTo("create_event"); + assertThat(definition.responseExampleAsString()).isNotEmpty(); + } + + private CreateEventResponse call(CreateEventRequest.Builder requestBuilder) { + CreateEventRequest request = requestBuilder.build(); + TestRequest httpRequest = ws.newRequest() + .setMethod(POST.name()) + .setMediaType(MediaTypes.PROTOBUF); + + httpRequest.setParam(PARAM_CATEGORY, request.getCategory().name()) + .setParam(PARAM_NAME, request.getName()) + .setParam(PARAM_ANALYSIS, request.getAnalysis()); + + if (request.getDescription() != null) { + httpRequest.setParam(PARAM_DESCRIPTION, request.getDescription()); + } + + try { + return CreateEventResponse.parseFrom(httpRequest.execute().getInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } +} |