diff options
17 files changed, 906 insertions, 150 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java index ea62814d830..6a454ee2720 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java @@ -38,6 +38,10 @@ public class CustomMeasureDao implements DaoComponent { mapper(session).insert(customMeasureDto); } + public void update(DbSession session, CustomMeasureDto customMeasure) { + mapper(session).update(customMeasure); + } + public void delete(DbSession session, long id) { mapper(session).delete(id); } @@ -60,7 +64,7 @@ public class CustomMeasureDao implements DaoComponent { public CustomMeasureDto selectById(DbSession session, long id) { CustomMeasureDto customMeasure = selectNullableById(session, id); if (customMeasure == null) { - throw new NotFoundException(String.format("CustomMeasure '%d' not found", id)); + throw new NotFoundException(String.format("Custom measure '%d' not found.", id)); } return customMeasure; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java index 4586689a37e..af5c91f22ef 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java @@ -20,10 +20,7 @@ package org.sonar.server.measure.custom.ws; -import com.google.common.base.Joiner; import java.net.HttpURLConnection; -import org.sonar.api.PropertyType; -import org.sonar.api.measures.Metric; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -40,9 +37,11 @@ import org.sonar.server.db.DbClient; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.ServerException; import org.sonar.server.user.UserSession; -import org.sonar.server.util.TypeValidations; +import org.sonar.server.user.index.UserDoc; +import org.sonar.server.user.index.UserIndex; import static com.google.common.base.Preconditions.checkArgument; +import static org.sonar.server.measure.custom.ws.CustomMeasureValueDescription.measureValueDescription; public class CreateAction implements CustomMeasuresWsAction { public static final String ACTION = "create"; @@ -53,28 +52,21 @@ public class CreateAction implements CustomMeasuresWsAction { public static final String PARAM_VALUE = "value"; public static final String PARAM_DESCRIPTION = "description"; - private static final String FIELD_ID = "id"; - private static final String FIELD_PROJECT_ID = PARAM_PROJECT_ID; - private static final String FIELD_PROJECT_KEY = PARAM_PROJECT_KEY; - private static final String FIELD_VALUE = PARAM_VALUE; - private static final String FIELD_DESCRIPTION = PARAM_DESCRIPTION; - private static final String FIELD_METRIC = "metric"; - private static final String FIELD_METRIC_KEY = "key"; - private static final String FIELD_METRIC_ID = "id"; - private static final String FIELD_METRIC_TYPE = "type"; - private final DbClient dbClient; private final UserSession userSession; private final System2 system; - private final TypeValidations typeValidations; + private final CustomMeasureValidator validator; private final CustomMeasureJsonWriter customMeasureJsonWriter; + private final UserIndex userIndex; - public CreateAction(DbClient dbClient, UserSession userSession, System2 system, TypeValidations typeValidations, CustomMeasureJsonWriter customMeasureJsonWriter) { + public CreateAction(DbClient dbClient, UserSession userSession, System2 system, CustomMeasureValidator validator, CustomMeasureJsonWriter customMeasureJsonWriter, + UserIndex userIndex) { this.dbClient = dbClient; this.userSession = userSession; this.system = system; - this.typeValidations = typeValidations; + this.validator = validator; this.customMeasureJsonWriter = customMeasureJsonWriter; + this.userIndex = userIndex; } @Override @@ -116,6 +108,7 @@ public class CreateAction implements CustomMeasuresWsAction { @Override public void handle(Request request, Response response) throws Exception { DbSession dbSession = dbClient.openSession(false); + String valueAsString = request.mandatoryParam(PARAM_VALUE); String description = request.param(PARAM_DESCRIPTION); long now = system.now(); @@ -124,18 +117,20 @@ public class CreateAction implements CustomMeasuresWsAction { MetricDto metric = searchMetric(dbSession, request); checkPermissions(component); checkMeasureDoesNotExistAlready(dbSession, component, metric); + UserDoc user = userIndex.getByLogin(userSession.getLogin()); CustomMeasureDto measure = new CustomMeasureDto() .setComponentUuid(component.uuid()) .setComponentId(component.getId()) .setMetricId(metric.getId()) .setDescription(description) + .setUserLogin(user.login()) .setCreatedAt(now); - setMeasureValue(measure, request, metric); + validator.setMeasureValue(measure, valueAsString, metric); dbClient.customMeasureDao().insert(dbSession, measure); dbSession.commit(); JsonWriter json = response.newJsonWriter(); - writeMeasure(json, measure, component, metric, request.mandatoryParam(PARAM_VALUE)); + customMeasureJsonWriter.write(json, measure, metric, component, user); json.close(); } finally { MyBatis.closeQuietly(dbSession); @@ -158,71 +153,6 @@ public class CreateAction implements CustomMeasuresWsAction { } } - private void writeMeasure(JsonWriter json, CustomMeasureDto measure, ComponentDto component, MetricDto metric, String measureWithoutInternalFormatting) { - customMeasureJsonWriter.write(json, measure, metric, component); - } - - private void setMeasureValue(CustomMeasureDto measure, Request request, MetricDto metric) { - String valueAsString = request.mandatoryParam(PARAM_VALUE); - Metric.ValueType metricType = Metric.ValueType.valueOf(metric.getValueType()); - try { - switch (metricType) { - case BOOL: - checkAndSetBooleanMeasureValue(measure, valueAsString); - break; - case INT: - case MILLISEC: - checkAndSetIntegerMeasureValue(measure, valueAsString); - break; - case WORK_DUR: - checkAndSetWorkDurationMeasureValue(measure, valueAsString); - break; - case FLOAT: - case PERCENT: - case RATING: - checkAndSetFloatMeasureValue(measure, valueAsString); - break; - case LEVEL: - checkAndSetLevelMeasureValue(measure, valueAsString); - break; - case STRING: - case DATA: - case DISTRIB: - measure.setTextValue(valueAsString); - break; - default: - throw new IllegalArgumentException("Unsupported metric type:" + metricType.description()); - } - } catch (Exception e) { - throw new IllegalArgumentException(String.format("Ill formatted value '%s' for metric type '%s'", valueAsString, metricType.description()), e); - } - } - - private void checkAndSetLevelMeasureValue(CustomMeasureDto measure, String valueAsString) { - typeValidations.validate(valueAsString, PropertyType.METRIC_LEVEL.name(), null); - measure.setTextValue(valueAsString); - } - - private void checkAndSetFloatMeasureValue(CustomMeasureDto measure, String valueAsString) { - typeValidations.validate(valueAsString, PropertyType.FLOAT.name(), null); - measure.setValue(Double.parseDouble(valueAsString)); - } - - private void checkAndSetWorkDurationMeasureValue(CustomMeasureDto measure, String valueAsString) { - typeValidations.validate(valueAsString, PropertyType.LONG.name(), null); - measure.setValue(Long.parseLong(valueAsString)); - } - - private void checkAndSetIntegerMeasureValue(CustomMeasureDto measure, String valueAsString) { - typeValidations.validate(valueAsString, PropertyType.INTEGER.name(), null); - measure.setValue(Integer.parseInt(valueAsString)); - } - - private void checkAndSetBooleanMeasureValue(CustomMeasureDto measure, String valueAsString) { - typeValidations.validate(valueAsString, PropertyType.BOOLEAN.name(), null); - measure.setValue(Boolean.parseBoolean(valueAsString) ? 1.0d : 0.0d); - } - private MetricDto searchMetric(DbSession dbSession, Request request) { Integer metricId = request.paramAsInt(PARAM_METRIC_ID); String metricKey = request.param(PARAM_METRIC_KEY); @@ -256,45 +186,4 @@ public class CreateAction implements CustomMeasuresWsAction { return project; } - - private static String measureValueDescription() { - StringBuilder description = new StringBuilder("Measure value. Value type depends on metric type:"); - description.append("<ul>"); - for (Metric.ValueType metricType : Metric.ValueType.values()) { - description.append("<li>"); - description.append(String.format("%s - %s", metricType.description(), metricTypeWsDescription(metricType))); - description.append("</li>"); - } - description.append("</ul>"); - - return description.toString(); - } - - private static String metricTypeWsDescription(Metric.ValueType metricType) { - switch (metricType) { - case BOOL: - return "the possible values are true or false"; - case INT: - case MILLISEC: - return "type: integer"; - case FLOAT: - case PERCENT: - case RATING: - return "type: double"; - case LEVEL: - return "the possible values are " + formattedMetricLevelNames(); - case STRING: - case DATA: - case DISTRIB: - return "type: string"; - case WORK_DUR: - return "long representing the number of minutes"; - default: - return "metric type not supported"; - } - } - - private static String formattedMetricLevelNames() { - return Joiner.on(", ").join(Metric.Level.names()); - } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java index cbb6211c5fb..0f359ff9bc1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java @@ -20,17 +20,22 @@ package org.sonar.server.measure.custom.ws; +import java.util.Date; import org.sonar.api.measures.Metric; +import org.sonar.api.server.ServerSide; +import org.sonar.api.user.User; import org.sonar.api.utils.text.JsonWriter; import org.sonar.core.component.ComponentDto; import org.sonar.core.measure.custom.db.CustomMeasureDto; import org.sonar.core.metric.db.MetricDto; +import org.sonar.server.user.ws.UserJsonWriter; import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_DESCRIPTION; import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_ID; import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_KEY; import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_VALUE; +@ServerSide public class CustomMeasureJsonWriter { private static final String FIELD_ID = "id"; private static final String FIELD_PROJECT_ID = PARAM_PROJECT_ID; @@ -41,8 +46,17 @@ public class CustomMeasureJsonWriter { private static final String FIELD_METRIC_KEY = "key"; private static final String FIELD_METRIC_ID = "id"; private static final String FIELD_METRIC_TYPE = "type"; + private static final String FIELD_CREATED_AT = "createdAt"; + private static final String FIELD_UPDATED_AT = "updatedAt"; + private static final String FIELD_USER = "user"; - public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component) { + private final UserJsonWriter userJsonWriter; + + public CustomMeasureJsonWriter(UserJsonWriter userJsonWriter) { + this.userJsonWriter = userJsonWriter; + } + + public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component, User user) { json.beginObject(); json.prop(FIELD_ID, String.valueOf(measure.getId())); json.name(FIELD_METRIC); @@ -51,6 +65,10 @@ public class CustomMeasureJsonWriter { json.prop(FIELD_PROJECT_KEY, component.key()); json.prop(FIELD_DESCRIPTION, measure.getDescription()); json.prop(FIELD_VALUE, measureValue(measure, metric)); + json.propDateTime(FIELD_CREATED_AT, new Date(measure.getCreatedAt())); + json.propDateTime(FIELD_UPDATED_AT, new Date(measure.getUpdatedAt())); + json.name(FIELD_USER); + userJsonWriter.write(json, user); json.endObject(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java new file mode 100644 index 00000000000..2d38586da01 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java @@ -0,0 +1,98 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.measure.custom.ws; + +import org.sonar.api.PropertyType; +import org.sonar.api.measures.Metric; +import org.sonar.api.server.ServerSide; +import org.sonar.core.measure.custom.db.CustomMeasureDto; +import org.sonar.core.metric.db.MetricDto; +import org.sonar.server.util.TypeValidations; + +@ServerSide +public class CustomMeasureValidator { + private final TypeValidations typeValidations; + + public CustomMeasureValidator(TypeValidations typeValidations) { + this.typeValidations = typeValidations; + } + + public void setMeasureValue(CustomMeasureDto measure, String valueAsString, MetricDto metric) { + Metric.ValueType metricType = Metric.ValueType.valueOf(metric.getValueType()); + try { + switch (metricType) { + case BOOL: + checkAndSetBooleanMeasureValue(measure, valueAsString); + break; + case INT: + case MILLISEC: + checkAndSetIntegerMeasureValue(measure, valueAsString); + break; + case WORK_DUR: + checkAndSetLongMeasureValue(measure, valueAsString); + break; + case FLOAT: + case PERCENT: + case RATING: + checkAndSetFloatMeasureValue(measure, valueAsString); + break; + case LEVEL: + checkAndSetLevelMeasureValue(measure, valueAsString); + break; + case STRING: + case DATA: + case DISTRIB: + measure.setTextValue(valueAsString); + break; + default: + throw new IllegalArgumentException("Unsupported metric type:" + metricType.description()); + } + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Ill formatted value '%s' for metric type '%s'", valueAsString, metricType.description()), e); + } + + } + + private void checkAndSetLevelMeasureValue(CustomMeasureDto measure, String valueAsString) { + typeValidations.validate(valueAsString, PropertyType.METRIC_LEVEL.name(), null); + measure.setTextValue(valueAsString); + } + + private void checkAndSetFloatMeasureValue(CustomMeasureDto measure, String valueAsString) { + typeValidations.validate(valueAsString, PropertyType.FLOAT.name(), null); + measure.setValue(Double.parseDouble(valueAsString)); + } + + private void checkAndSetLongMeasureValue(CustomMeasureDto measure, String valueAsString) { + typeValidations.validate(valueAsString, PropertyType.LONG.name(), null); + measure.setValue(Long.parseLong(valueAsString)); + } + + private void checkAndSetIntegerMeasureValue(CustomMeasureDto measure, String valueAsString) { + typeValidations.validate(valueAsString, PropertyType.INTEGER.name(), null); + measure.setValue(Integer.parseInt(valueAsString)); + } + + private void checkAndSetBooleanMeasureValue(CustomMeasureDto measure, String valueAsString) { + typeValidations.validate(valueAsString, PropertyType.BOOLEAN.name(), null); + measure.setValue(Boolean.parseBoolean(valueAsString) ? 1.0d : 0.0d); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java new file mode 100644 index 00000000000..2970556b81e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java @@ -0,0 +1,71 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.measure.custom.ws; + +import com.google.common.base.Joiner; +import org.sonar.api.measures.Metric; + +class CustomMeasureValueDescription { + private CustomMeasureValueDescription() { + // utility class + } + + static String measureValueDescription() { + StringBuilder description = new StringBuilder("Measure value. Value type depends on metric type:"); + description.append("<ul>"); + for (Metric.ValueType metricType : Metric.ValueType.values()) { + description.append("<li>"); + description.append(String.format("%s - %s", metricType.description(), metricTypeWsDescription(metricType))); + description.append("</li>"); + } + description.append("</ul>"); + + return description.toString(); + } + + private static String metricTypeWsDescription(Metric.ValueType metricType) { + switch (metricType) { + case BOOL: + return "the possible values are true or false"; + case INT: + case MILLISEC: + return "type: integer"; + case FLOAT: + case PERCENT: + case RATING: + return "type: double"; + case LEVEL: + return "the possible values are " + formattedMetricLevelNames(); + case STRING: + case DATA: + case DISTRIB: + return "type: string"; + case WORK_DUR: + return "long representing the number of minutes"; + default: + return "metric type not supported"; + } + } + + private static String formattedMetricLevelNames() { + return Joiner.on(", ").join(Metric.Level.names()); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java index ae49ecf5007..51270b190fd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java @@ -27,8 +27,10 @@ public class CustomMeasuresWsModule extends Module { protected void configureModule() { add( CustomMeasuresWs.class, + CreateAction.class, + UpdateAction.class, DeleteAction.class, CustomMeasureJsonWriter.class, - CreateAction.class); + CustomMeasureValidator.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java new file mode 100644 index 00000000000..3dbf8a4e4c9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java @@ -0,0 +1,143 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.measure.custom.ws; + +import javax.annotation.Nullable; +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.user.User; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.measure.custom.db.CustomMeasureDto; +import org.sonar.core.metric.db.MetricDto; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; +import org.sonar.server.user.UserSession; +import org.sonar.server.user.index.UserIndex; + +import static org.sonar.server.measure.custom.ws.CustomMeasureValueDescription.measureValueDescription; + +public class UpdateAction implements CustomMeasuresWsAction { + public static final String ACTION = "update"; + public static final String PARAM_ID = "id"; + public static final String PARAM_VALUE = "value"; + public static final String PARAM_DESCRIPTION = "description"; + + private final DbClient dbClient; + private final UserSession userSession; + private final System2 system; + private final CustomMeasureValidator validator; + private final CustomMeasureJsonWriter customMeasureJsonWriter; + private final UserIndex userIndex; + + public UpdateAction(DbClient dbClient, UserSession userSession, System2 system, CustomMeasureValidator validator, CustomMeasureJsonWriter customMeasureJsonWriter, UserIndex userIndex) { + this.dbClient = dbClient; + this.userSession = userSession; + this.system = system; + this.validator = validator; + this.customMeasureJsonWriter = customMeasureJsonWriter; + this.userIndex = userIndex; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(ACTION) + .setPost(true) + .setDescription("Update a custom measure. Value and/or description must be provided<br />" + + "Requires 'Administer System' permission or 'Administer' permission on the project.") + .setHandler(this) + .setSince("5.2"); + + action.createParam(PARAM_ID) + .setRequired(true) + .setDescription("id") + .setExampleValue("42"); + + action.createParam(PARAM_VALUE) + .setExampleValue("true") + .setDescription(measureValueDescription()); + + action.createParam(PARAM_DESCRIPTION) + .setExampleValue("Team size growing."); + } + + @Override + public void handle(Request request, Response response) throws Exception { + int id = request.mandatoryParamAsInt(PARAM_ID); + String value = request.param(PARAM_VALUE); + String description = request.param(PARAM_DESCRIPTION); + checkParameters(value, description); + + DbSession dbSession = dbClient.openSession(true); + try { + CustomMeasureDto customMeasure = dbClient.customMeasureDao().selectById(dbSession, id); + MetricDto metric = dbClient.metricDao().selectById(dbSession, customMeasure.getMetricId()); + ComponentDto component = dbClient.componentDao().selectByUuid(dbSession, customMeasure.getComponentUuid()); + User user = userIndex.getByLogin(userSession.getLogin()); + + checkPermissions(component); + + setValue(customMeasure, value, metric); + setDescription(customMeasure, description); + customMeasure.setUserLogin(user.login()); + customMeasure.setUpdatedAt(system.now()); + dbClient.customMeasureDao().update(dbSession, customMeasure); + dbSession.commit(); + + JsonWriter json = response.newJsonWriter(); + customMeasureJsonWriter.write(json, customMeasure, metric, component, user); + json.close(); + } finally { + MyBatis.closeQuietly(dbSession); + } + } + + private void setValue(CustomMeasureDto customMeasure, @Nullable String value, MetricDto metric) { + if (value != null) { + validator.setMeasureValue(customMeasure, value, metric); + } + } + + private static void setDescription(CustomMeasureDto customMeasure, @Nullable String description) { + if (description != null) { + customMeasure.setDescription(description); + } + } + + private static void checkParameters(@Nullable String value, @Nullable String description) { + if (value == null && description == null) { + throw new IllegalArgumentException("Value or description must be provided."); + } + } + + private void checkPermissions(ComponentDto component) { + if (userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN)) { + return; + } + + userSession.checkLoggedIn().checkProjectUuidPermission(UserRole.ADMIN, component.projectUuid()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java index 6fccdd1153d..fdc8581c9f0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java @@ -20,16 +20,17 @@ package org.sonar.server.measure.custom.ws; -import java.util.Arrays; import java.util.List; import org.assertj.core.data.Offset; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; +import org.sonar.api.config.Settings; import org.sonar.api.measures.Metric; import org.sonar.api.measures.Metric.ValueType; import org.sonar.api.utils.System2; @@ -42,6 +43,7 @@ import org.sonar.core.persistence.DbTester; import org.sonar.server.component.ComponentTesting; import org.sonar.server.component.db.ComponentDao; import org.sonar.server.db.DbClient; +import org.sonar.server.es.EsTester; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.ServerException; @@ -49,17 +51,16 @@ import org.sonar.server.measure.custom.persistence.CustomMeasureDao; import org.sonar.server.metric.persistence.MetricDao; import org.sonar.server.metric.ws.MetricTesting; import org.sonar.server.tester.UserSessionRule; -import org.sonar.server.util.BooleanTypeValidation; -import org.sonar.server.util.FloatTypeValidation; -import org.sonar.server.util.IntegerTypeValidation; -import org.sonar.server.util.LongTypeValidation; -import org.sonar.server.util.MetricLevelTypeValidation; -import org.sonar.server.util.TypeValidations; +import org.sonar.server.user.index.UserDoc; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.user.index.UserIndexDefinition; +import org.sonar.server.user.ws.UserJsonWriter; import org.sonar.server.ws.WsTester; import org.sonar.test.DbTests; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Offset.offset; +import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations; @Category(DbTests.class) public class CreateActionTest { @@ -72,20 +73,30 @@ public class CreateActionTest { public ExpectedException expectedException = ExpectedException.none(); @ClassRule public static DbTester db = new DbTester(); + @ClassRule + public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings())); DbClient dbClient; DbSession dbSession; WsTester ws; + @BeforeClass + public static void setUpClass() throws Exception { + es.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, new UserDoc() + .setLogin("login") + .setName("Login") + .setEmail("login@login.com") + .setActive(true)); + } + @Before public void setUp() { dbClient = new DbClient(db.database(), db.myBatis(), new CustomMeasureDao(), new MetricDao(), new ComponentDao()); dbSession = dbClient.openSession(false); - TypeValidations typeValidations = new TypeValidations(Arrays.asList(new BooleanTypeValidation(), new IntegerTypeValidation(), new FloatTypeValidation(), - new MetricLevelTypeValidation(), new LongTypeValidation())); - ws = new WsTester(new CustomMeasuresWs(new CreateAction(dbClient, userSession, System2.INSTANCE, typeValidations, new CustomMeasureJsonWriter()))); + ws = new WsTester(new CustomMeasuresWs(new CreateAction(dbClient, userSession, System2.INSTANCE, new CustomMeasureValidator(newFullTypeValidations()), + new CustomMeasureJsonWriter(new UserJsonWriter(userSession)), new UserIndex(es.client())))); db.truncateTables(); - userSession.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + userSession.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); } @After @@ -186,7 +197,7 @@ public class CreateActionTest { } @Test - public void create_float_custom_measure_indb() throws Exception { + public void create_float_custom_measure_in_db() throws Exception { MetricDto metric = insertMetricAndProject(ValueType.FLOAT, DEFAULT_PROJECT_UUID); newRequest() diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java new file mode 100644 index 00000000000..c546022eee8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java @@ -0,0 +1,116 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.measure.custom.ws; + +import org.assertj.core.data.Offset; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.core.measure.custom.db.CustomMeasureDto; +import org.sonar.server.measure.custom.persistence.CustomMeasureTesting; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.measures.Metric.Level.WARN; +import static org.sonar.api.measures.Metric.ValueType.BOOL; +import static org.sonar.api.measures.Metric.ValueType.FLOAT; +import static org.sonar.api.measures.Metric.ValueType.INT; +import static org.sonar.api.measures.Metric.ValueType.LEVEL; +import static org.sonar.api.measures.Metric.ValueType.STRING; +import static org.sonar.api.measures.Metric.ValueType.WORK_DUR; +import static org.sonar.server.metric.ws.MetricTesting.newMetricDto; +import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations; + +public class CustomMeasureValidatorTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + CustomMeasureValidator sut = new CustomMeasureValidator(newFullTypeValidations()); + CustomMeasureDto customMeasure = CustomMeasureTesting.newCustomMeasureDto(); + + @Test + public void set_boolean_true_value() { + sut.setMeasureValue(customMeasure, "true", newMetricDto().setValueType(BOOL.name())); + + assertThat(customMeasure.getValue()).isCloseTo(1.0d, defaultOffset()); + } + + @Test + public void set_boolean_false_value() { + sut.setMeasureValue(customMeasure, "false", newMetricDto().setValueType(BOOL.name())); + + assertThat(customMeasure.getValue()).isCloseTo(0.0d, defaultOffset()); + } + + @Test + public void set_integer_value() { + sut.setMeasureValue(customMeasure, "1984", newMetricDto().setValueType(INT.name())); + + assertThat(customMeasure.getValue()).isCloseTo(1984d, defaultOffset()); + } + + @Test + public void set_float_value() { + sut.setMeasureValue(customMeasure, "3.14", newMetricDto().setValueType(FLOAT.name())); + + assertThat(customMeasure.getValue()).isCloseTo(3.14d, defaultOffset()); + } + + @Test + public void set_long_value() { + sut.setMeasureValue(customMeasure, "123456789", newMetricDto().setValueType(WORK_DUR.name())); + + assertThat(customMeasure.getValue()).isCloseTo(123456789d, defaultOffset()); + } + + @Test + public void set_level_value() { + sut.setMeasureValue(customMeasure, WARN.name(), newMetricDto().setValueType(LEVEL.name())); + + assertThat(customMeasure.getTextValue()).isEqualTo(WARN.name()); + } + + @Test + public void set_string_value() { + sut.setMeasureValue(customMeasure, "free-text-string", newMetricDto().setValueType(STRING.name())); + + assertThat(customMeasure.getTextValue()).isEqualTo("free-text-string"); + } + + @Test + public void fail_when_non_compliant_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Ill formatted value 'non-compliant-boolean-value' for metric type 'Yes/No'"); + + sut.setMeasureValue(customMeasure, "non-compliant-boolean-value", newMetricDto().setValueType(BOOL.name())); + } + + @Test + public void fail_when_non_compliant_level_value() { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Ill formatted value 'non-compliant-level-value' for metric type 'Level'"); + + sut.setMeasureValue(customMeasure, "non-compliant-level-value", newMetricDto().setValueType(LEVEL.name())); + } + + private Offset<Double> defaultOffset() { + return Offset.offset(0.01d); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java index 098819b9cb0..7a65f3a7b07 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java @@ -30,6 +30,6 @@ public class CustomMeasuresWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new CustomMeasuresWsModule().configure(container); - assertThat(container.size()).isEqualTo(5); + assertThat(container.size()).isEqualTo(8); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java index 3737f9236ed..7ed41878f21 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java @@ -26,11 +26,12 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.server.db.DbClient; import org.sonar.server.user.UserSession; -import org.sonar.server.util.TypeValidations; +import org.sonar.server.user.index.UserIndex; import org.sonar.server.ws.WsTester; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.sonar.server.measure.custom.ws.CustomMeasuresWs.ENDPOINT; public class CustomMeasuresWsTest { WsTester ws; @@ -41,7 +42,8 @@ public class CustomMeasuresWsTest { UserSession userSession = mock(UserSession.class); ws = new WsTester(new CustomMeasuresWs( new DeleteAction(dbClient, userSession), - new CreateAction(dbClient, userSession, System2.INSTANCE, mock(TypeValidations.class), mock(CustomMeasureJsonWriter.class)) + new CreateAction(dbClient, userSession, System2.INSTANCE, mock(CustomMeasureValidator.class), mock(CustomMeasureJsonWriter.class), mock(UserIndex.class)), + new UpdateAction(dbClient, userSession, System2.INSTANCE, mock(CustomMeasureValidator.class), mock(CustomMeasureJsonWriter.class), mock(UserIndex.class)) )); } @@ -50,18 +52,24 @@ public class CustomMeasuresWsTest { WebService.Controller controller = ws.controller("api/custom_measures"); assertThat(controller).isNotNull(); assertThat(controller.description()).isNotEmpty(); - assertThat(controller.actions()).hasSize(2); + assertThat(controller.actions()).hasSize(3); } @Test public void delete_action_properties() { - WebService.Action deleteAction = ws.controller("api/custom_measures").action("delete"); + WebService.Action deleteAction = ws.controller(ENDPOINT).action("delete"); assertThat(deleteAction.isPost()).isTrue(); } @Test public void create_action_properties() { - WebService.Action action = ws.controller("api/custom_measures").action("create"); + WebService.Action action = ws.controller(ENDPOINT).action("create"); + assertThat(action.isPost()).isTrue(); + } + + @Test + public void create_update_properties() { + WebService.Action action = ws.controller(ENDPOINT).action("update"); assertThat(action.isPost()).isTrue(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java new file mode 100644 index 00000000000..188dd68ea11 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java @@ -0,0 +1,321 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.measure.custom.ws; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.Metric.ValueType; +import org.sonar.api.utils.System2; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.measure.custom.db.CustomMeasureDto; +import org.sonar.core.metric.db.MetricDto; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.server.component.ComponentTesting; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.es.EsTester; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.ServerException; +import org.sonar.server.measure.custom.persistence.CustomMeasureDao; +import org.sonar.server.metric.persistence.MetricDao; +import org.sonar.server.metric.ws.MetricTesting; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.user.index.UserDoc; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.user.index.UserIndexDefinition; +import org.sonar.server.user.ws.UserJsonWriter; +import org.sonar.server.ws.WsTester; +import org.sonar.test.DbTests; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.data.Offset.offset; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.measure.custom.persistence.CustomMeasureTesting.newCustomMeasureDto; +import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_DESCRIPTION; +import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_ID; +import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_VALUE; +import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations; + +@Category(DbTests.class) +public class UpdateActionTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @ClassRule + public static DbTester db = new DbTester(); + @ClassRule + public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings())); + DbClient dbClient; + DbSession dbSession; + System2 system; + WsTester ws; + + @BeforeClass + public static void setUpClass() throws Exception { + es.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, new UserDoc() + .setLogin("login") + .setName("Login") + .setEmail("login@login.com") + .setActive(true)); + } + + @Before + public void setUp() throws Exception { + dbClient = new DbClient(db.database(), db.myBatis(), new CustomMeasureDao(), new ComponentDao(), new MetricDao()); + dbSession = dbClient.openSession(false); + db.truncateTables(); + system = mock(System2.class); + CustomMeasureValidator validator = new CustomMeasureValidator(newFullTypeValidations()); + ws = new WsTester(new CustomMeasuresWs(new UpdateAction(dbClient, userSessionRule, system, validator, new CustomMeasureJsonWriter(new UserJsonWriter(userSessionRule)), + new UserIndex(es.client())))); + userSessionRule.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + } + + @After + public void tearDown() { + dbSession.close(); + } + + @Test + public void update_text_value_and_description_in_db() throws Exception { + MetricDto metric = insertNewMetric(ValueType.STRING); + ComponentDto component = insertNewProject("project-uuid"); + CustomMeasureDto customMeasure = newCustomMeasure(component, metric) + .setDescription("custom-measure-description") + .setTextValue("text-measure-value"); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + when(system.now()).thenReturn(123_456_789L); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "new-text-measure-value") + .execute(); + + CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId()); + assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("new-text-measure-value"); + assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description"); + assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L); + assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt()); + } + + @Test + public void update_double_value_and_description_in_db() throws Exception { + MetricDto metric = insertNewMetric(ValueType.INT); + ComponentDto component = insertNewProject("project-uuid"); + CustomMeasureDto customMeasure = newCustomMeasure(component, metric) + .setDescription("custom-measure-description") + .setValue(42d); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "1984") + .execute(); + + CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId()); + assertThat(updatedCustomMeasure.getValue()).isCloseTo(1984d, offset(0.01d)); + assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description"); + assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt()); + } + + @Test + public void returns_full_object_in_response() throws Exception { + MetricDto metric = MetricTesting.newMetricDto().setEnabled(true) + .setValueType(ValueType.STRING.name()) + .setKey("metric-key"); + dbClient.metricDao().insert(dbSession, metric); + ComponentDto component = ComponentTesting.newProjectDto("project-uuid").setKey("project-key"); + dbClient.componentDao().insert(dbSession, component); + CustomMeasureDto customMeasure = newCustomMeasure(component, metric) + .setCreatedAt(100_000_000L) + .setDescription("custom-measure-description") + .setTextValue("text-measure-value"); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + when(system.now()).thenReturn(123_456_789L); + + WsTester.Result response = ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "new-text-measure-value") + .execute(); + + response.assertJson(getClass(), "custom-measure.json"); + String responseAsString = response.outputAsString(); + assertThat(responseAsString).matches(String.format(".*\"id\"\\s*:\\s*\"%s\".*", customMeasure.getId())); + assertThat(responseAsString).matches(String.format(".*\"id\"\\s*:\\s*\"%s\".*", metric.getId())); + assertThat(responseAsString).matches(".*createdAt.*updatedAt.*"); + } + + @Test + public void update_value_only() throws Exception { + MetricDto metric = insertNewMetric(ValueType.STRING); + ComponentDto component = insertNewProject("project-uuid"); + CustomMeasureDto customMeasure = newCustomMeasure(component, metric) + .setDescription("custom-measure-description") + .setTextValue("text-measure-value"); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + when(system.now()).thenReturn(123_456_789L); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .execute(); + + CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId()); + assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("text-measure-value"); + assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description"); + assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L); + assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt()); + } + + @Test + public void update_description_only() throws Exception { + MetricDto metric = insertNewMetric(ValueType.STRING); + ComponentDto component = insertNewProject("project-uuid"); + CustomMeasureDto customMeasure = newCustomMeasure(component, metric) + .setMetricId(metric.getId()) + .setComponentId(component.getId()) + .setComponentUuid(component.uuid()) + .setCreatedAt(system.now()) + .setDescription("custom-measure-description") + .setTextValue("text-measure-value"); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + when(system.now()).thenReturn(123_456_789L); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_VALUE, "new-text-measure-value") + .execute(); + + CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId()); + assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("new-text-measure-value"); + assertThat(updatedCustomMeasure.getDescription()).isEqualTo("custom-measure-description"); + assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L); + assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt()); + } + + @Test + public void fail_if_get_request() throws Exception { + expectedException.expect(ServerException.class); + + ws.newGetRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, "42") + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "1984") + .execute(); + } + + @Test + public void fail_if_not_in_db() throws Exception { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("Custom measure '42' not found."); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, "42") + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "1984") + .execute(); + } + + @Test + public void fail_if_insufficient_privileges() throws Exception { + userSessionRule.login("login").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + expectedException.expect(ForbiddenException.class); + MetricDto metric = MetricTesting.newMetricDto().setEnabled(true).setValueType(ValueType.STRING.name()); + dbClient.metricDao().insert(dbSession, metric); + ComponentDto component = ComponentTesting.newProjectDto("project-uuid"); + dbClient.componentDao().insert(dbSession, component); + CustomMeasureDto customMeasure = newCustomMeasureDto() + .setMetricId(metric.getId()) + .setComponentId(component.getId()) + .setComponentUuid(component.uuid()) + .setCreatedAt(system.now()) + .setDescription("custom-measure-description") + .setTextValue("text-measure-value"); + dbClient.customMeasureDao().insert(dbSession, customMeasure); + dbSession.commit(); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, String.valueOf(customMeasure.getId())) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "1984") + .execute(); + } + + @Test + public void fail_if_custom_measure_id_is_missing_in_request() throws Exception { + expectedException.expect(IllegalArgumentException.class); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_DESCRIPTION, "new-custom-measure-description") + .setParam(PARAM_VALUE, "1984") + .execute(); + } + + @Test + public void fail_if_custom_measure_value_and_description_are_missing_in_request() throws Exception { + expectedException.expect(IllegalArgumentException.class); + + ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION) + .setParam(PARAM_ID, "42") + .execute(); + } + + private MetricDto insertNewMetric(ValueType metricType) { + MetricDto metric = MetricTesting.newMetricDto().setEnabled(true).setValueType(metricType.name()); + dbClient.metricDao().insert(dbSession, metric); + + return metric; + } + + private ComponentDto insertNewProject(String uuid) { + ComponentDto component = ComponentTesting.newProjectDto(uuid); + dbClient.componentDao().insert(dbSession, component); + return component; + } + + private CustomMeasureDto newCustomMeasure(ComponentDto project, MetricDto metric) { + return newCustomMeasureDto() + .setMetricId(metric.getId()) + .setComponentId(project.getId()) + .setComponentUuid(project.uuid()) + .setCreatedAt(system.now()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java b/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java new file mode 100644 index 00000000000..a4e31d7d8f2 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java @@ -0,0 +1,41 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonar.server.util; + +import java.util.Arrays; + +public class TypeValidationsTesting { + private TypeValidationsTesting() { + // utility class + } + + public static TypeValidations newFullTypeValidations() { + return new TypeValidations(Arrays.asList( + new BooleanTypeValidation(), + new IntegerTypeValidation(), + new LongTypeValidation(), + new FloatTypeValidation(), + new StringTypeValidation(), + new StringListTypeValidation(), + new MetricLevelTypeValidation() + )); + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json index 97ddaa1775f..ab5e1c12003 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json +++ b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json @@ -6,5 +6,11 @@ "type": "STRING" }, "value": "custom-measure-free-text", - "description": "custom-measure-description" + "description": "custom-measure-description", + "user": { + "active": true, + "email": "login@login.com", + "login": "login", + "name": "Login" + } } diff --git a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json new file mode 100644 index 00000000000..a66612bb9b0 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json @@ -0,0 +1,16 @@ +{ + "projectId": "project-uuid", + "projectKey": "project-key", + "metric": { + "key": "metric-key", + "type": "STRING" + }, + "value": "new-text-measure-value", + "description": "new-custom-measure-description", + "user": { + "active": true, + "email": "login@login.com", + "login": "login", + "name": "Login" + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java b/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java index bfc7eb929ea..444753996da 100644 --- a/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java @@ -24,7 +24,11 @@ import java.util.List; import org.apache.ibatis.annotations.Param; public interface CustomMeasureMapper { - void insert(CustomMeasureDto customMeasureDto); + void insert(CustomMeasureDto customMeasure); + + void update(CustomMeasureDto customMeasure); + + void delete(long id); void deleteByMetricIds(@Param("metricIds") List<Integer> metricIds); @@ -34,7 +38,5 @@ public interface CustomMeasureMapper { List<CustomMeasureDto> selectByComponentUuid(String s); - void delete(long id); - int countByComponentIdAndMetricId(@Param("componentUuid") String componentUuid, @Param("metricId") int metricId); } diff --git a/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml b/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml index 12a61bdf359..f9062d192d6 100644 --- a/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml @@ -47,6 +47,16 @@ ) </insert> + <update id="update" parameterType="CustomMeasure"> + update manual_measures + set value = #{value, jdbcType=DOUBLE}, + text_value = #{textValue, jdbcType=VARCHAR}, + description = #{description, jdbcType=VARCHAR}, + user_login = #{userLogin, jdbcType=VARCHAR}, + updated_at = #{updatedAt, jdbcType=BIGINT} + where id = #{id} + </update> + <delete id="deleteByMetricIds"> delete from manual_measures where metric_id in |