3 * Copyright (C) 2009-2021 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.measure.ws;
22 import java.util.List;
23 import java.util.stream.LongStream;
24 import org.junit.Before;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.junit.rules.ExpectedException;
28 import org.sonar.api.measures.Metric.ValueType;
29 import org.sonar.api.server.ws.WebService;
30 import org.sonar.api.server.ws.WebService.Param;
31 import org.sonar.api.utils.System2;
32 import org.sonar.api.web.UserRole;
33 import org.sonar.core.util.stream.MoreCollectors;
34 import org.sonar.db.DbClient;
35 import org.sonar.db.DbSession;
36 import org.sonar.db.DbTester;
37 import org.sonar.db.component.ComponentDto;
38 import org.sonar.db.component.SnapshotDto;
39 import org.sonar.db.measure.MeasureDto;
40 import org.sonar.db.metric.MetricDto;
41 import org.sonar.server.component.TestComponentFinder;
42 import org.sonar.server.exceptions.ForbiddenException;
43 import org.sonar.server.exceptions.NotFoundException;
44 import org.sonar.server.measure.ws.SearchHistoryAction.SearchHistoryRequest;
45 import org.sonar.server.tester.UserSessionRule;
46 import org.sonar.server.ws.TestRequest;
47 import org.sonar.server.ws.WsActionTester;
48 import org.sonarqube.ws.Common.Paging;
49 import org.sonarqube.ws.Measures.SearchHistoryResponse;
50 import org.sonarqube.ws.Measures.SearchHistoryResponse.HistoryMeasure;
51 import org.sonarqube.ws.Measures.SearchHistoryResponse.HistoryValue;
53 import static java.lang.Double.parseDouble;
54 import static java.lang.String.format;
55 import static java.util.Arrays.asList;
56 import static java.util.Collections.singletonList;
57 import static java.util.Optional.ofNullable;
58 import static org.assertj.core.api.Assertions.assertThat;
59 import static org.assertj.core.api.Assertions.tuple;
60 import static org.sonar.api.utils.DateUtils.formatDateTime;
61 import static org.sonar.api.utils.DateUtils.parseDateTime;
62 import static org.sonar.db.component.BranchType.PULL_REQUEST;
63 import static org.sonar.db.component.ComponentTesting.newFileDto;
64 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
65 import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
66 import static org.sonar.db.component.SnapshotTesting.newAnalysis;
67 import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
68 import static org.sonar.db.metric.MetricTesting.newMetricDto;
69 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_BRANCH;
70 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_COMPONENT;
71 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_FROM;
72 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_METRICS;
73 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_PULL_REQUEST;
74 import static org.sonar.server.component.ws.MeasuresWsParameters.PARAM_TO;
75 import static org.sonar.test.JsonAssert.assertJson;
77 public class SearchHistoryActionTest {
80 public UserSessionRule userSession = UserSessionRule.standalone();
82 public ExpectedException expectedException = ExpectedException.none();
84 public DbTester db = DbTester.create();
86 private final DbClient dbClient = db.getDbClient();
87 private final DbSession dbSession = db.getSession();
89 private final WsActionTester ws = new WsActionTester(new SearchHistoryAction(dbClient, TestComponentFinder.from(db), userSession));
91 private ComponentDto project;
92 private SnapshotDto analysis;
93 private MetricDto complexityMetric;
94 private MetricDto nclocMetric;
95 private MetricDto newViolationMetric;
96 private MetricDto stringMetric;
100 project = newPrivateProjectDto();
101 analysis = db.components().insertProjectAndSnapshot(project);
102 userSession.addProjectPermission(UserRole.USER, project);
103 nclocMetric = insertNclocMetric();
104 complexityMetric = insertComplexityMetric();
105 newViolationMetric = insertNewViolationMetric();
106 stringMetric = insertStringMetric();
110 public void empty_response() {
111 project = db.components().insertPrivateProject();
112 userSession.addProjectPermission(UserRole.USER, project);
113 SearchHistoryRequest request = SearchHistoryRequest.builder()
114 .setComponent(project.getDbKey())
115 .setMetrics(singletonList(complexityMetric.getKey()))
118 SearchHistoryResponse result = call(request);
120 assertThat(result.getMeasuresList()).hasSize(1);
121 assertThat(result.getMeasures(0).getHistoryCount()).isZero();
123 assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
124 // pagination is applied to the number of analyses
125 .containsExactly(1, 100, 0);
129 public void analyses_but_no_measure() {
130 project = db.components().insertPrivateProject();
131 analysis = db.components().insertSnapshot(project);
132 userSession.addProjectPermission(UserRole.USER, project);
134 SearchHistoryRequest request = SearchHistoryRequest.builder()
135 .setComponent(project.getDbKey())
136 .setMetrics(singletonList(complexityMetric.getKey()))
139 SearchHistoryResponse result = call(request);
141 assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsExactly(1, 100, 1);
142 assertThat(result.getMeasuresList()).hasSize(1);
143 assertThat(result.getMeasures(0).getHistoryList()).extracting(HistoryValue::hasDate, HistoryValue::hasValue).containsExactly(tuple(true, false));
147 public void return_metrics() {
148 dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, analysis).setValue(42.0d));
151 SearchHistoryRequest request = SearchHistoryRequest.builder()
152 .setComponent(project.getDbKey())
153 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey()))
156 SearchHistoryResponse result = call(request);
158 assertThat(result.getMeasuresList()).hasSize(3)
159 .extracting(HistoryMeasure::getMetric)
160 .containsExactly(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey());
164 public void return_measures() {
165 SnapshotDto laterAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(analysis.getCreatedAt() + 42_000));
166 ComponentDto file = db.components().insertComponent(newFileDto(project));
167 dbClient.measureDao().insert(dbSession,
168 newMeasureDto(complexityMetric, project, analysis).setValue(101d),
169 newMeasureDto(complexityMetric, project, laterAnalysis).setValue(100d),
170 newMeasureDto(complexityMetric, file, analysis).setValue(42d),
171 newMeasureDto(nclocMetric, project, analysis).setValue(201d),
172 newMeasureDto(newViolationMetric, project, analysis).setVariation(5d),
173 newMeasureDto(newViolationMetric, project, laterAnalysis).setVariation(10d));
176 SearchHistoryRequest request = SearchHistoryRequest.builder()
177 .setComponent(project.getDbKey())
178 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey()))
180 SearchHistoryResponse result = call(request);
182 assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal)
183 .containsExactly(1, 100, 2);
184 assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(3)
185 .containsExactly(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey());
186 String analysisDate = formatDateTime(analysis.getCreatedAt());
187 String laterAnalysisDate = formatDateTime(laterAnalysis.getCreatedAt());
188 // complexity measures
189 HistoryMeasure complexityMeasures = result.getMeasures(0);
190 assertThat(complexityMeasures.getMetric()).isEqualTo(complexityMetric.getKey());
191 assertThat(complexityMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue)
192 .containsExactly(tuple(analysisDate, "101"), tuple(laterAnalysisDate, "100"));
194 HistoryMeasure nclocMeasures = result.getMeasures(1);
195 assertThat(nclocMeasures.getMetric()).isEqualTo(nclocMetric.getKey());
196 assertThat(nclocMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue, HistoryValue::hasValue).containsExactly(
197 tuple(analysisDate, "201", true), tuple(laterAnalysisDate, "", false));
198 // new_violation measures
199 HistoryMeasure newViolationMeasures = result.getMeasures(2);
200 assertThat(newViolationMeasures.getMetric()).isEqualTo(newViolationMetric.getKey());
201 assertThat(newViolationMeasures.getHistoryList()).extracting(HistoryValue::getDate, HistoryValue::getValue)
202 .containsExactly(tuple(analysisDate, "5"), tuple(laterAnalysisDate, "10"));
206 public void pagination_applies_to_analyses() {
207 project = db.components().insertPrivateProject();
208 userSession.addProjectPermission(UserRole.USER, project);
209 List<String> analysisDates = LongStream.rangeClosed(1, 9)
210 .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(i * 1_000_000_000)))
211 .peek(a -> dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, a).setValue(101d)))
212 .map(a -> formatDateTime(a.getCreatedAt()))
213 .collect(MoreCollectors.toList());
216 SearchHistoryRequest request = SearchHistoryRequest.builder()
217 .setComponent(project.getDbKey())
218 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey()))
222 SearchHistoryResponse result = call(request);
224 assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsExactly(2, 3, 9);
225 assertThat(result.getMeasures(0).getHistoryList()).extracting(HistoryValue::getDate).containsExactly(
226 analysisDates.get(3), analysisDates.get(4), analysisDates.get(5));
230 public void inclusive_from_and_to_dates() {
231 project = db.components().insertPrivateProject();
232 userSession.addProjectPermission(UserRole.USER, project);
233 List<String> analysisDates = LongStream.rangeClosed(1, 9)
234 .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(System2.INSTANCE.now() + i * 1_000_000_000L)))
235 .peek(a -> dbClient.measureDao().insert(dbSession, newMeasureDto(complexityMetric, project, a).setValue(Double.valueOf(a.getCreatedAt()))))
236 .map(a -> formatDateTime(a.getCreatedAt()))
237 .collect(MoreCollectors.toList());
240 SearchHistoryRequest request = SearchHistoryRequest.builder()
241 .setComponent(project.getDbKey())
242 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey()))
243 .setFrom(analysisDates.get(1))
244 .setTo(analysisDates.get(3))
246 SearchHistoryResponse result = call(request);
248 assertThat(result.getPaging()).extracting(Paging::getPageIndex, Paging::getPageSize, Paging::getTotal).containsExactly(1, 100, 3);
249 assertThat(result.getMeasures(0).getHistoryList()).extracting(HistoryValue::getDate).containsExactly(
250 analysisDates.get(1), analysisDates.get(2), analysisDates.get(3));
254 public void return_best_values_for_files() {
255 dbClient.metricDao().insert(dbSession, newMetricDto().setKey("optimized").setValueType(ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(456d));
256 dbClient.metricDao().insert(dbSession, newMetricDto().setKey("new_optimized").setValueType(ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(789d));
258 ComponentDto file = db.components().insertComponent(newFileDto(project));
260 SearchHistoryRequest request = SearchHistoryRequest.builder()
261 .setComponent(file.getDbKey())
262 .setMetrics(asList("optimized", "new_optimized"))
264 SearchHistoryResponse result = call(request);
266 assertThat(result.getMeasuresCount()).isEqualTo(2);
267 assertThat(result.getMeasuresList().get(0).getHistoryList()).extracting(HistoryValue::getValue).containsExactly("789");
268 assertThat(result.getMeasuresList().get(1).getHistoryList()).extracting(HistoryValue::getValue).containsExactly("456");
270 // Best value is not applied to project
271 request = SearchHistoryRequest.builder()
272 .setComponent(project.getDbKey())
273 .setMetrics(asList("optimized", "new_optimized"))
275 result = call(request);
276 assertThat(result.getMeasuresList().get(0).getHistoryCount()).isEqualTo(1);
277 assertThat(result.getMeasuresList().get(0).getHistory(0).hasDate()).isTrue();
278 assertThat(result.getMeasuresList().get(0).getHistory(0).hasValue()).isFalse();
282 public void do_not_return_unprocessed_analyses() {
283 dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setStatus(STATUS_UNPROCESSED));
286 SearchHistoryRequest request = SearchHistoryRequest.builder()
287 .setComponent(project.getDbKey())
288 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey()))
290 SearchHistoryResponse result = call(request);
292 // one analysis in setUp method
293 assertThat(result.getPaging().getTotal()).isEqualTo(1);
297 public void branch() {
298 ComponentDto project = db.components().insertPrivateProject();
299 userSession.addProjectPermission(UserRole.USER, project);
300 ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
301 ComponentDto file = db.components().insertComponent(newFileDto(branch));
302 SnapshotDto analysis = db.components().insertSnapshot(branch);
303 MeasureDto measure = db.measures().insertMeasure(file, analysis, nclocMetric, m -> m.setValue(2d));
305 SearchHistoryResponse result = ws.newRequest()
306 .setParam(PARAM_COMPONENT, file.getKey())
307 .setParam(PARAM_BRANCH, "my_branch")
308 .setParam(PARAM_METRICS, "ncloc")
309 .executeProtobuf(SearchHistoryResponse.class);
311 assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(1);
312 HistoryMeasure historyMeasure = result.getMeasures(0);
313 assertThat(historyMeasure.getMetric()).isEqualTo(nclocMetric.getKey());
314 assertThat(historyMeasure.getHistoryList())
315 .extracting(m -> parseDouble(m.getValue()))
316 .containsExactlyInAnyOrder(measure.getValue());
320 public void pull_request() {
321 ComponentDto project = db.components().insertPrivateProject();
322 userSession.addProjectPermission(UserRole.USER, project);
323 ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("pr-123").setBranchType(PULL_REQUEST));
324 ComponentDto file = db.components().insertComponent(newFileDto(branch));
325 SnapshotDto analysis = db.components().insertSnapshot(branch);
326 MeasureDto measure = db.measures().insertMeasure(file, analysis, nclocMetric, m -> m.setValue(2d));
328 SearchHistoryResponse result = ws.newRequest()
329 .setParam(PARAM_COMPONENT, file.getKey())
330 .setParam(PARAM_PULL_REQUEST, "pr-123")
331 .setParam(PARAM_METRICS, "ncloc")
332 .executeProtobuf(SearchHistoryResponse.class);
334 assertThat(result.getMeasuresList()).extracting(HistoryMeasure::getMetric).hasSize(1);
335 HistoryMeasure historyMeasure = result.getMeasures(0);
336 assertThat(historyMeasure.getMetric()).isEqualTo(nclocMetric.getKey());
337 assertThat(historyMeasure.getHistoryList())
338 .extracting(m -> parseDouble(m.getValue()))
339 .containsExactlyInAnyOrder(measure.getValue());
343 public void fail_when_using_branch_db_key() {
344 ComponentDto project = db.components().insertPrivateProject();
345 userSession.logIn().addProjectPermission(UserRole.USER, project);
346 ComponentDto branch = db.components().insertProjectBranch(project);
348 expectedException.expect(NotFoundException.class);
349 expectedException.expectMessage(format("Component key '%s' not found", branch.getDbKey()));
352 .setParam(PARAM_COMPONENT, branch.getDbKey())
353 .setParam(PARAM_METRICS, "ncloc")
358 public void fail_if_unknown_metric() {
359 SearchHistoryRequest request = SearchHistoryRequest.builder()
360 .setComponent(project.getDbKey())
361 .setMetrics(asList(complexityMetric.getKey(), nclocMetric.getKey(), "METRIC_42", "42_METRIC"))
364 expectedException.expect(IllegalArgumentException.class);
365 expectedException.expectMessage("Metrics 42_METRIC, METRIC_42 are not found");
371 public void fail_if_not_enough_permissions() {
372 userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
373 SearchHistoryRequest request = SearchHistoryRequest.builder()
374 .setComponent(project.getDbKey())
375 .setMetrics(singletonList(complexityMetric.getKey()))
378 expectedException.expect(ForbiddenException.class);
384 public void fail_if_unknown_component() {
385 SearchHistoryRequest request = SearchHistoryRequest.builder()
386 .setComponent("__UNKNOWN__")
387 .setMetrics(singletonList(complexityMetric.getKey()))
390 expectedException.expect(NotFoundException.class);
396 public void fail_when_component_is_removed() {
397 ComponentDto project = db.components().insertComponent(newPrivateProjectDto());
398 db.components().insertComponent(newFileDto(project).setDbKey("file-key").setEnabled(false));
399 userSession.addProjectPermission(UserRole.USER, project);
401 expectedException.expect(NotFoundException.class);
402 expectedException.expectMessage("Component key 'file-key' not found");
405 .setParam(PARAM_COMPONENT, "file-key")
406 .setParam(PARAM_METRICS, "ncloc")
411 public void fail_if_branch_does_not_exist() {
412 ComponentDto project = db.components().insertPrivateProject();
413 ComponentDto file = db.components().insertComponent(newFileDto(project));
414 userSession.addProjectPermission(UserRole.USER, project);
415 db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
417 expectedException.expect(NotFoundException.class);
418 expectedException.expectMessage(String.format("Component '%s' on branch '%s' not found", file.getKey(), "another_branch"));
421 .setParam(PARAM_COMPONENT, file.getKey())
422 .setParam(PARAM_BRANCH, "another_branch")
423 .setParam(PARAM_METRICS, "ncloc")
428 public void definition() {
429 WebService.Action definition = ws.getDef();
431 assertThat(definition.key()).isEqualTo("search_history");
432 assertThat(definition.responseExampleAsString()).isNotEmpty();
433 assertThat(definition.isPost()).isFalse();
434 assertThat(definition.isInternal()).isFalse();
435 assertThat(definition.since()).isEqualTo("6.3");
436 assertThat(definition.params()).hasSize(8);
438 Param branch = definition.param("branch");
439 assertThat(branch.since()).isEqualTo("6.6");
440 assertThat(branch.isInternal()).isFalse();
441 assertThat(branch.isRequired()).isFalse();
445 public void json_example() {
446 project = db.components().insertPrivateProject();
447 userSession.addProjectPermission(UserRole.USER, project);
448 long now = parseDateTime("2017-01-23T17:00:53+0100").getTime();
449 LongStream.rangeClosed(0, 2)
450 .mapToObj(i -> dbClient.snapshotDao().insert(dbSession, newAnalysis(project).setCreatedAt(now + i * 24 * 1_000 * 60 * 60)))
451 .forEach(analysis -> dbClient.measureDao().insert(dbSession,
452 newMeasureDto(complexityMetric, project, analysis).setValue(45d),
453 newMeasureDto(newViolationMetric, project, analysis).setVariation(46d),
454 newMeasureDto(nclocMetric, project, analysis).setValue(47d)));
457 String result = ws.newRequest()
458 .setParam(PARAM_COMPONENT, project.getDbKey())
459 .setParam(PARAM_METRICS, String.join(",", asList(complexityMetric.getKey(), nclocMetric.getKey(), newViolationMetric.getKey())))
460 .execute().getInput();
462 assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
466 public void measure_without_values() {
467 dbClient.measureDao().insert(dbSession, newMeasureDto(stringMetric, project, analysis).setValue(null).setData(null));
470 SearchHistoryRequest request = SearchHistoryRequest.builder()
471 .setComponent(project.getDbKey())
472 .setMetrics(singletonList(stringMetric.getKey()))
474 SearchHistoryResponse result = call(request);
476 HistoryMeasure measure = result.getMeasuresList().stream()
477 .filter(m -> m.getMetric().equals(stringMetric.getKey()))
480 assertThat(measure.getHistoryList()).hasSize(1);
481 assertThat(measure.getHistory(0).hasValue()).isFalse();
484 private SearchHistoryResponse call(SearchHistoryRequest request) {
485 TestRequest testRequest = ws.newRequest();
487 testRequest.setParam(PARAM_COMPONENT, request.getComponent());
488 testRequest.setParam(PARAM_METRICS, String.join(",", request.getMetrics()));
489 ofNullable(request.getFrom()).ifPresent(from -> testRequest.setParam(PARAM_FROM, from));
490 ofNullable(request.getTo()).ifPresent(to -> testRequest.setParam(PARAM_TO, to));
491 ofNullable(request.getPage()).ifPresent(p -> testRequest.setParam(Param.PAGE, String.valueOf(p)));
492 ofNullable(request.getPageSize()).ifPresent(ps -> testRequest.setParam(Param.PAGE_SIZE, String.valueOf(ps)));
494 return testRequest.executeProtobuf(SearchHistoryResponse.class);
497 private static MetricDto newMetricDtoWithoutOptimization() {
498 return newMetricDto()
500 .setOptimizedBestValue(false)
502 .setUserManaged(false);
505 private MetricDto insertNclocMetric() {
506 MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
508 .setShortName("Lines of code")
509 .setDescription("Non Commenting Lines of Code")
513 .setQualitative(false)
515 .setUserManaged(false));
520 private MetricDto insertComplexityMetric() {
521 MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
522 .setKey("complexity")
523 .setShortName("Complexity")
524 .setDescription("Cyclomatic complexity")
525 .setDomain("Complexity")
528 .setQualitative(false)
530 .setUserManaged(false));
535 private MetricDto insertNewViolationMetric() {
536 MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
537 .setKey("new_violations")
538 .setShortName("New issues")
539 .setDescription("New Issues")
543 .setQualitative(true)
545 .setUserManaged(false));
550 private MetricDto insertStringMetric() {
551 MetricDto metric = dbClient.metricDao().insert(dbSession, newMetricDtoWithoutOptimization()
553 .setShortName("A String")
554 .setValueType("STRING"));