From fbb33db724f16444ac882a4ffe88d15e8ff39837 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Tue, 6 Dec 2016 18:34:44 +0100 Subject: [PATCH] SONAR-8464 Create WS api/project_analyses/create_event --- .../measure/ws/ComponentTreeAction.java | 6 +- .../platformlevel/PlatformLevel4.java | 4 + .../ProjectAnalysisModule.java | 37 ++ .../server/projectanalysis/package-info.java | 25 ++ .../projectanalysis/ws/CreateEventAction.java | 214 +++++++++++ .../projectanalysis/ws/ProjectAnalysesWs.java | 45 +++ .../ws/ProjectAnalysesWsAction.java | 27 ++ .../projectanalysis/ws/package-info.java | 25 ++ .../ws/create_event-example.json | 9 + .../ProjectAnalysisModuleTest.java | 36 ++ .../ws/CreateEventActionTest.java | 351 ++++++++++++++++++ .../org/sonar/db/component/SnapshotDao.java | 4 + .../sonar/db/component/SnapshotMapper.java | 2 + .../java/org/sonar/db/event/EventDao.java | 11 +- .../java/org/sonar/db/event/EventDto.java | 10 +- .../java/org/sonar/db/event/EventMapper.java | 5 +- .../org/sonar/db/event/EventValidator.java | 60 +++ .../org/sonar/db/component/SnapshotMapper.xml | 7 +- .../org/sonar/db/event/EventMapper.xml | 9 + .../src/test/java/org/sonar/db/DbTester.java | 7 + .../sonar/db/component/SnapshotDaoTest.java | 13 + .../java/org/sonar/db/event/EventDaoTest.java | 28 ++ .../org/sonar/db/event/EventDbTester.java | 45 +++ .../java/org/sonar/db/event/EventTesting.java | 45 +++ .../sonar/db/event/EventValidatorTest.java | 64 ++++ .../projectanalysis/CreateEventRequest.java | 124 +++++++ .../ProjectAnalysesWsParameters.java | 32 ++ .../client/projectanalysis/package-info.java | 25 ++ .../main/protobuf/ws-projectanalyses.proto | 38 ++ .../CreateEventRequestTest.java | 78 ++++ .../ProjectAnalysesWsParametersTest.java | 34 ++ 31 files changed, 1410 insertions(+), 10 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/CreateEventAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWs.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/ProjectAnalysesWsAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/package-info.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/create_event-example.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/CreateEventActionTest.java create mode 100644 sonar-db/src/main/java/org/sonar/db/event/EventValidator.java create mode 100644 sonar-db/src/test/java/org/sonar/db/event/EventDbTester.java create mode 100644 sonar-db/src/test/java/org/sonar/db/event/EventTesting.java create mode 100644 sonar-db/src/test/java/org/sonar/db/event/EventValidatorTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParameters.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/package-info.java create mode 100644 sonar-ws/src/main/protobuf/ws-projectanalyses.proto create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequestTest.java create mode 100644 sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParametersTest.java 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.
" + "Requires one of the following permissions:" + "" + "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.
" + + "Requires one of the following permissions:" + + "") + .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 dbEvents = dbClient.eventDao().selectByAnalysisUuid(dbSession, analysis.getUuid()); + Predicate similarEventExisting = filterSimilarEvents(request); + dbEvents.stream() + .filter(similarEventExisting) + .findAny() + .ifPresent(throwException(request)); + } + + private static Predicate 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 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 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 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 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); + } + } +} diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java index c1a5eabda55..f9f6f716355 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java +++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotDao.java @@ -138,6 +138,10 @@ public class SnapshotDao implements Dao { insert(session, Lists.asList(item, others)); } + public void updateVersion(DbSession dbSession, String analysisUuid, String version) { + mapper(dbSession).updateVersion(analysisUuid, version); + } + /** * Used by Governance */ diff --git a/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java index c5c7aef8f5c..52185ad878e 100644 --- a/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/component/SnapshotMapper.java @@ -55,4 +55,6 @@ public interface SnapshotMapper { void unsetIsLastFlagForComponentUuid(@Param("componentUuid") String componentUuid); void setIsLastFlagForAnalysisUuid(@Param("analysisUuid") String analysisUuid); + + void updateVersion(@Param("analysisUuid") String analysisUuid, @Param("version") String version); } diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventDao.java b/sonar-db/src/main/java/org/sonar/db/event/EventDao.java index b28b33e1647..3c91a35d15f 100644 --- a/sonar-db/src/main/java/org/sonar/db/event/EventDao.java +++ b/sonar-db/src/main/java/org/sonar/db/event/EventDao.java @@ -29,12 +29,21 @@ public class EventDao implements Dao { return session.getMapper(EventMapper.class).selectByComponentUuid(componentUuid); } - public void insert(DbSession session, EventDto dto) { + public List selectByAnalysisUuid(DbSession dbSession, String uuid) { + return mapper(dbSession).selectByAnalysisUuid(uuid); + } + + public EventDto insert(DbSession session, EventDto dto) { session.getMapper(EventMapper.class).insert(dto); + + return dto; } public void delete(DbSession session, Long id) { session.getMapper(EventMapper.class).delete(id); } + private static EventMapper mapper(DbSession session) { + return session.getMapper(EventMapper.class); + } } diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventDto.java b/sonar-db/src/main/java/org/sonar/db/event/EventDto.java index 18e2845f5af..58aba3b8fac 100644 --- a/sonar-db/src/main/java/org/sonar/db/event/EventDto.java +++ b/sonar-db/src/main/java/org/sonar/db/event/EventDto.java @@ -22,6 +22,10 @@ package org.sonar.db.event; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import static org.sonar.db.event.EventValidator.checkEventCategory; +import static org.sonar.db.event.EventValidator.checkEventDescription; +import static org.sonar.db.event.EventValidator.checkEventName; + public class EventDto { public static final String CATEGORY_VERSION = "Version"; @@ -80,7 +84,7 @@ public class EventDto { } public EventDto setName(String name) { - this.name = name; + this.name = checkEventName(name); return this; } @@ -89,7 +93,7 @@ public class EventDto { } public EventDto setCategory(String category) { - this.category = category; + this.category = checkEventCategory(category); return this; } @@ -127,7 +131,7 @@ public class EventDto { } public EventDto setDescription(@Nullable String description) { - this.description = description; + this.description = checkEventDescription(description); return this; } diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java b/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java index d7a9190abbc..c8eb24e680e 100644 --- a/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java +++ b/sonar-db/src/main/java/org/sonar/db/event/EventMapper.java @@ -23,10 +23,11 @@ import java.util.List; public interface EventMapper { - List selectByComponentUuid(String uuid); + List selectByComponentUuid(String componentUuid); + + List selectByAnalysisUuid(String analysisUuid); void insert(EventDto dto); void delete(long id); - } diff --git a/sonar-db/src/main/java/org/sonar/db/event/EventValidator.java b/sonar-db/src/main/java/org/sonar/db/event/EventValidator.java new file mode 100644 index 00000000000..61d7f0907fc --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/event/EventValidator.java @@ -0,0 +1,60 @@ +/* + * 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.db.event; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; + +public class EventValidator { + private static final int MAX_NAME_LENGTH = 400; + private static final int MAX_CATEGORY_LENGTH = 50; + private static final int MAX_DESCRIPTION_LENGTH = 4000; + + private EventValidator() { + // prevent instantiation + } + + public static String checkEventName(String name) { + checkArgument(name.length() <= MAX_NAME_LENGTH, "Event name length (%s) is longer than the maximum authorized (%s). '%s' was provided.", + name.length(), MAX_NAME_LENGTH, name); + return name; + } + + public static String checkEventCategory(String category) { + checkArgument(category.length() <= MAX_CATEGORY_LENGTH, "Event category length (%s) is longer than the maximum authorized (%s). '%s' was provided.", + category.length(), MAX_CATEGORY_LENGTH, category); + return category; + } + + @CheckForNull + public static String checkEventDescription(@Nullable String description) { + if (description == null) { + return null; + } + + checkArgument(description.length() <= MAX_DESCRIPTION_LENGTH, "Event description length (%s) is longer than the maximum authorized (%s). '%s' was provided.", + description.length(), MAX_DESCRIPTION_LENGTH, description); + return description; + } + +} diff --git a/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml index 98166bd23c1..b27c1e31abf 100644 --- a/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/component/SnapshotMapper.xml @@ -177,6 +177,12 @@ where uuid = #{analysisUuid} + + update snapshots + set version = #{version, jdbcType=VARCHAR} + where uuid = #{analysisUuid} + + insert into snapshots ( uuid, @@ -227,6 +233,5 @@ #{period4Date, jdbcType=BIGINT}, #{period5Date, jdbcType=BIGINT}) - diff --git a/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml b/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml index ad24d2d78aa..4f4fd4d6a2e 100644 --- a/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml +++ b/sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml @@ -24,6 +24,15 @@ + + INSERT INTO events (uuid, analysis_uuid, component_uuid, name, category, description, event_data, event_date, created_at) VALUES ( diff --git a/sonar-db/src/test/java/org/sonar/db/DbTester.java b/sonar-db/src/test/java/org/sonar/db/DbTester.java index e3fde304403..3b32b88b9a4 100644 --- a/sonar-db/src/test/java/org/sonar/db/DbTester.java +++ b/sonar-db/src/test/java/org/sonar/db/DbTester.java @@ -65,6 +65,7 @@ import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Loggers; import org.sonar.core.util.SequenceUuidFactory; import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.event.EventDbTester; import org.sonar.db.organization.OrganizationDbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.organization.OrganizationTesting; @@ -99,6 +100,7 @@ public class DbTester extends ExternalResource { private final UserDbTester userTester; private final ComponentDbTester componentTester; + private final EventDbTester eventTester; private final OrganizationDbTester organizationTester; private final PermissionTemplateDbTester permissionTemplateTester; private final QualityGateDbTester qualityGateDbTester; @@ -110,6 +112,7 @@ public class DbTester extends ExternalResource { initDbClient(); this.userTester = new UserDbTester(this); this.componentTester = new ComponentDbTester(this); + this.eventTester = new EventDbTester(this); this.organizationTester = new OrganizationDbTester(this); this.permissionTemplateTester = new PermissionTemplateDbTester(this); this.qualityGateDbTester = new QualityGateDbTester(this); @@ -177,6 +180,10 @@ public class DbTester extends ExternalResource { return componentTester; } + public EventDbTester events() { + return eventTester; + } + public OrganizationDbTester organizations() { return organizationTester; } diff --git a/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java index be33663ff03..dac0d64a2d5 100644 --- a/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/component/SnapshotDaoTest.java @@ -35,6 +35,7 @@ import static com.google.common.collect.Lists.newArrayList; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED; import static org.sonar.db.component.SnapshotQuery.SORT_FIELD.BY_DATE; import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.ASC; import static org.sonar.db.component.SnapshotQuery.SORT_ORDER.DESC; @@ -296,6 +297,18 @@ public class SnapshotDaoTest { verifyStatusAndIsLastFlag("A3", SnapshotDto.STATUS_PROCESSED, true); } + @Test + public void updateVersion() { + insertAnalysis("P1", "A1", STATUS_PROCESSED, true); + db.commit(); + + underTest.updateVersion(dbSession, "A1", "5.6.3"); + + SnapshotDto result = underTest.selectByUuid(dbSession, "A1").get(); + + assertThat(result.getVersion()).isEqualTo("5.6.3"); + } + private void insertAnalysis(String projectUuid, String uuid, String status, boolean isLastFlag) { SnapshotDto snapshot = SnapshotTesting.newAnalysis(ComponentTesting.newProjectDto(projectUuid)) .setLast(isLastFlag) diff --git a/sonar-db/src/test/java/org/sonar/db/event/EventDaoTest.java b/sonar-db/src/test/java/org/sonar/db/event/EventDaoTest.java index c61b79b2120..1bd351dbf6c 100644 --- a/sonar-db/src/test/java/org/sonar/db/event/EventDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/event/EventDaoTest.java @@ -24,9 +24,16 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; +import static org.sonar.db.event.EventTesting.newEvent; public class EventDaoTest { @@ -34,6 +41,8 @@ public class EventDaoTest { public ExpectedException expectedException = ExpectedException.none(); @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbTester.getSession(); EventDao underTest = dbTester.getDbClient().eventDao(); @@ -60,6 +69,25 @@ public class EventDaoTest { assertThat(dto.getCreatedAt()).isEqualTo(1225630680000L); } + @Test + public void select_by_analysis_uuid() { + ComponentDto project = newProjectDto(); + SnapshotDto analysis = dbTester.components().insertProjectAndSnapshot(project); + SnapshotDto otherAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(project)); + dbTester.commit(); + dbTester.events().insertEvent(newEvent(analysis).setUuid("A1")); + dbTester.events().insertEvent(newEvent(otherAnalysis).setUuid("O1")); + dbTester.events().insertEvent(newEvent(analysis).setUuid("A2")); + dbTester.events().insertEvent(newEvent(otherAnalysis).setUuid("O2")); + dbTester.events().insertEvent(newEvent(analysis).setUuid("A3")); + dbTester.events().insertEvent(newEvent(otherAnalysis).setUuid("O3")); + + List result = underTest.selectByAnalysisUuid(dbSession, analysis.getUuid()); + + assertThat(result).hasSize(3); + assertThat(result).extracting(EventDto::getUuid).containsOnly("A1", "A2", "A3"); + } + @Test public void return_different_categories() { dbTester.prepareDbUnit(getClass(), "shared.xml"); diff --git a/sonar-db/src/test/java/org/sonar/db/event/EventDbTester.java b/sonar-db/src/test/java/org/sonar/db/event/EventDbTester.java new file mode 100644 index 00000000000..dc55f5cb11c --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/event/EventDbTester.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.db.event; + +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; + +public class EventDbTester { + private final DbTester db; + private final DbClient dbClient; + private final DbSession dbSession; + + public EventDbTester(DbTester db) { + this.db = db; + this.dbClient = db.getDbClient(); + this.dbSession = db.getSession(); + } + + public EventDto insertEvent(EventDto event) { + dbClient.eventDao().insert(dbSession, event); + db.commit(); + + return event; + } + +} diff --git a/sonar-db/src/test/java/org/sonar/db/event/EventTesting.java b/sonar-db/src/test/java/org/sonar/db/event/EventTesting.java new file mode 100644 index 00000000000..5df8844388f --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/event/EventTesting.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.db.event; + +import org.sonar.db.component.SnapshotDto; + +import static java.util.Objects.requireNonNull; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; + +public class EventTesting { + + public static EventDto newEvent(SnapshotDto analysis) { + requireNonNull(analysis.getUuid()); + requireNonNull(analysis.getComponentUuid()); + + return new EventDto() + .setAnalysisUuid(analysis.getUuid()) + .setComponentUuid(analysis.getComponentUuid()) + .setUuid(randomAlphanumeric(40)) + .setName(randomAlphanumeric(400)) + .setDescription(randomAlphanumeric(400)) + .setCategory(randomAlphanumeric(50)) + .setComponentUuid(analysis.getComponentUuid()) + .setCreatedAt(System.currentTimeMillis()) + .setDate(System.currentTimeMillis()); + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/event/EventValidatorTest.java b/sonar-db/src/test/java/org/sonar/db/event/EventValidatorTest.java new file mode 100644 index 00000000000..f40b459532d --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/event/EventValidatorTest.java @@ -0,0 +1,64 @@ +/* + * 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.db.event; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static com.google.common.base.Strings.repeat; + +public class EventValidatorTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void valid_cases() { + EventValidator.checkEventName(repeat("a", 400)); + EventValidator.checkEventCategory(repeat("a", 50)); + EventValidator.checkEventDescription(repeat("a", 4000)); + EventValidator.checkEventDescription(null); + } + + @Test + public void fail_if_name_longer_than_400() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Event name length (401) is longer than the maximum authorized (400)."); + + EventValidator.checkEventName(repeat("a", 400 + 1)); + } + + @Test + public void fail_if_category_longer_than_50() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Event category length (51) is longer than the maximum authorized (50). 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' was provided."); + + EventValidator.checkEventCategory(repeat("a", 50 + 1)); + } + + @Test + public void fail_if_description_longer_than_4000() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Event description length (4001) is longer than the maximum authorized (4000)."); + + EventValidator.checkEventDescription(repeat("a", 4000 + 1)); + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequest.java new file mode 100644 index 00000000000..a47238e59e2 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequest.java @@ -0,0 +1,124 @@ +/* + * 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.projectanalysis; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; + +public class CreateEventRequest { + private final String analysis; + private final Category category; + private final String name; + private final String description; + + private CreateEventRequest(Builder builder) { + analysis = builder.analysis; + category = builder.category; + name = builder.name; + description = builder.description; + } + + public String getAnalysis() { + return analysis; + } + + public Category getCategory() { + return category; + } + + public String getName() { + return name; + } + + @CheckForNull + public String getDescription() { + return description; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String analysis; + private Category category = Category.OTHER; + private String name; + private String description; + + private Builder() { + // enforce static factory method + } + + public Builder setAnalysis(String analysis) { + this.analysis = analysis; + return this; + } + + public Builder setCategory(Category category) { + this.category = category; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setDescription(@Nullable String description) { + this.description = description; + return this; + } + + public CreateEventRequest build() { + checkArgument(analysis != null, "Analysis key is required"); + checkArgument(category != null, "Category is required"); + checkArgument(name != null, "Name is required"); + + return new CreateEventRequest(this); + } + } + + public enum Category { + VERSION("Version"), OTHER("Other"); + + private final String label; + + Category(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public static Category fromLabel(String label) { + for (Category category : values()) { + if (category.getLabel().equals(label)) { + return category; + } + } + + throw new IllegalArgumentException("Unknown event category label '" + label + "'"); + } + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParameters.java new file mode 100644 index 00000000000..0b1cfd8429d --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParameters.java @@ -0,0 +1,32 @@ +/* + * 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.projectanalysis; + +public class ProjectAnalysesWsParameters { + public static final String PARAM_ANALYSIS = "analysis"; + public static final String PARAM_CATEGORY = "category"; + public static final String PARAM_NAME = "name"; + public static final String PARAM_DESCRIPTION = "description"; + + private ProjectAnalysesWsParameters() { + // static access only + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/package-info.java new file mode 100644 index 00000000000..cd3561af0f6 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/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.sonarqube.ws.client.projectanalysis; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-ws/src/main/protobuf/ws-projectanalyses.proto b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto new file mode 100644 index 00000000000..686027ad229 --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto @@ -0,0 +1,38 @@ +// SonarQube, open source software quality management tool. +// Copyright (C) 2008-2016 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. + +syntax = "proto2"; + +package sonarqube.ws.projectanalysis; + +option java_package = "org.sonarqube.ws"; +option java_outer_classname = "ProjectAnalyses"; +option optimize_for = SPEED; + +// WS api/project_analyses/create_event +message CreateEventResponse { + optional Event event = 1; +} + +message Event { + optional string key = 1; + optional string analysis = 2; + optional string category = 3; + optional string name = 4; + optional string description = 5; +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequestTest.java new file mode 100644 index 00000000000..a96e1e0ce30 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/CreateEventRequestTest.java @@ -0,0 +1,78 @@ +/* + * 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.projectanalysis; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.OTHER; +import static org.sonarqube.ws.client.projectanalysis.CreateEventRequest.Category.VERSION; + +public class CreateEventRequestTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CreateEventRequest.Builder underTest = CreateEventRequest.builder(); + + @Test + public void build_request() { + CreateEventRequest result = underTest.setAnalysis("P1").setCategory(OTHER).setName("name").setDescription("description").build(); + + assertThat(result.getAnalysis()).isEqualTo("P1"); + assertThat(result.getCategory()).isEqualTo(OTHER); + assertThat(result.getName()).isEqualTo("name"); + assertThat(result.getDescription()).isEqualTo("description"); + } + + @Test + public void other_category_by_default() { + CreateEventRequest result = underTest.setAnalysis("P1").setName("name").build(); + + assertThat(OTHER.equals(result.getCategory())); + } + + @Test + public void fail_when_no_category() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Category is required"); + + underTest.setAnalysis("P1").setName("name").setCategory(null).build(); + } + + @Test + public void fail_when_no_analysis() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Analysis key is required"); + + underTest.setCategory(VERSION).setName("name").build(); + } + + @Test + public void fail_when_no_name() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Name is required"); + + underTest.setAnalysis("P1").setCategory(VERSION).build(); + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParametersTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParametersTest.java new file mode 100644 index 00000000000..f431cbd0858 --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/ProjectAnalysesWsParametersTest.java @@ -0,0 +1,34 @@ +/* + * 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.projectanalysis; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.test.TestUtils.hasOnlyPrivateConstructors; + +public class ProjectAnalysesWsParametersTest { + + @Test + public void private_access_only() { + assertThat(hasOnlyPrivateConstructors(ProjectAnalysesWsParameters.class)).isTrue(); + } +} -- 2.39.5