3 * Copyright (C) 2009-2024 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.qualitygate.ws;
22 import java.io.IOException;
23 import java.nio.charset.StandardCharsets;
24 import org.apache.commons.io.IOUtils;
25 import org.apache.commons.lang3.RandomStringUtils;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.measures.CoreMetrics;
30 import org.sonar.api.server.ws.WebService;
31 import org.sonar.api.utils.System2;
32 import org.sonar.api.web.UserRole;
33 import org.sonar.db.DbClient;
34 import org.sonar.db.DbSession;
35 import org.sonar.db.DbTester;
36 import org.sonar.db.component.BranchType;
37 import org.sonar.db.component.ComponentDto;
38 import org.sonar.db.component.ProjectData;
39 import org.sonar.db.component.SnapshotDto;
40 import org.sonar.db.metric.MetricDto;
41 import org.sonar.db.permission.GlobalPermission;
42 import org.sonar.server.component.TestComponentFinder;
43 import org.sonar.server.exceptions.BadRequestException;
44 import org.sonar.server.exceptions.ForbiddenException;
45 import org.sonar.server.exceptions.NotFoundException;
46 import org.sonar.server.qualitygate.QualityGateCaycChecker;
47 import org.sonar.server.tester.UserSessionRule;
48 import org.sonar.server.ws.WsActionTester;
49 import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
50 import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status;
52 import static java.lang.String.format;
53 import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.assertj.core.api.Assertions.assertThatThrownBy;
56 import static org.assertj.core.api.Assertions.tuple;
57 import static org.junit.Assert.assertEquals;
58 import static org.mockito.ArgumentMatchers.any;
59 import static org.mockito.ArgumentMatchers.eq;
60 import static org.mockito.Mockito.mock;
61 import static org.mockito.Mockito.when;
62 import static org.sonar.db.component.SnapshotTesting.newAnalysis;
63 import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
64 import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
65 import static org.sonar.db.metric.MetricTesting.newMetricDto;
66 import static org.sonar.server.qualitygate.QualityGateCaycStatus.COMPLIANT;
67 import static org.sonar.server.qualitygate.QualityGateCaycStatus.NON_COMPLIANT;
68 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_ANALYSIS_ID;
69 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_BRANCH;
70 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_ID;
71 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_KEY;
72 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PULL_REQUEST;
73 import static org.sonar.test.JsonAssert.assertJson;
75 public class ProjectStatusActionIT {
76 private static final String ANALYSIS_ID = "task-uuid";
79 public UserSessionRule userSession = UserSessionRule.standalone();
81 public DbTester db = DbTester.create(System2.INSTANCE);
83 private final DbClient dbClient = db.getDbClient();
84 private final DbSession dbSession = db.getSession();
85 private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
87 private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker));
91 when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(), any())).thenReturn(NON_COMPLIANT);
95 public void test_definition() {
96 WebService.Action action = ws.getDef();
97 assertThat(action.params())
98 .extracting(WebService.Param::key, WebService.Param::isRequired)
99 .containsExactlyInAnyOrder(
100 tuple("analysisId", false),
101 tuple("projectKey", false),
102 tuple("projectId", false),
103 tuple("branch", false),
104 tuple("pullRequest", false));
108 public void test_json_example() throws IOException {
109 ProjectData projectData = db.components().insertPrivateProject();
110 ComponentDto mainBranch = projectData.getMainBranchComponent();
111 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
112 MetricDto gateDetailsMetric = insertGateDetailMetric();
114 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch)
115 .setPeriodMode("last_version")
116 .setPeriodParam("2015-12-07")
117 .setPeriodDate(956789123987L));
118 dbClient.measureDao().insert(dbSession,
119 newMeasureDto(gateDetailsMetric, mainBranch, snapshot)
120 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), StandardCharsets.UTF_8)));
123 String response = ws.newRequest()
124 .setParam("analysisId", snapshot.getUuid())
125 .execute().getInput();
127 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
131 public void return_past_status_when_project_is_referenced_by_past_analysis_id() throws IOException {
132 ProjectData projectData = db.components().insertPrivateProject();
133 ComponentDto mainBranch = projectData.getMainBranchComponent();
134 SnapshotDto pastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch)
136 .setPeriodMode("last_version")
137 .setPeriodParam("2015-12-07")
138 .setPeriodDate(956789123987L));
139 SnapshotDto lastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch)
141 .setPeriodMode("last_version")
142 .setPeriodParam("2016-12-07")
143 .setPeriodDate(1_500L));
144 MetricDto gateDetailsMetric = insertGateDetailMetric();
145 dbClient.measureDao().insert(dbSession,
146 newMeasureDto(gateDetailsMetric, mainBranch, pastAnalysis)
147 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
148 dbClient.measureDao().insert(dbSession,
149 newMeasureDto(gateDetailsMetric, mainBranch, lastAnalysis)
150 .setData("not_used"));
152 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
154 String response = ws.newRequest()
155 .setParam(PARAM_ANALYSIS_ID, pastAnalysis.getUuid())
156 .execute().getInput();
158 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
162 public void return_live_status_when_project_is_referenced_by_its_id() throws IOException {
163 ProjectData projectData = db.components().insertPrivateProject();
164 ComponentDto mainBranch = projectData.getMainBranchComponent();
165 dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch)
166 .setPeriodMode("last_version")
167 .setPeriodParam("2015-12-07")
168 .setPeriodDate(956789123987L));
169 MetricDto gateDetailsMetric = insertGateDetailMetric();
170 dbClient.liveMeasureDao().insert(dbSession,
171 newLiveMeasure(mainBranch, gateDetailsMetric)
172 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
174 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
176 String response = ws.newRequest()
177 .setParam(PARAM_PROJECT_ID, projectData.projectUuid())
178 .execute().getInput();
180 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
184 public void return_past_status_when_branch_is_referenced_by_past_analysis_id() throws IOException {
185 ProjectData projectData = db.components().insertPrivateProject();
186 ComponentDto mainBranch = projectData.getMainBranchComponent();
187 ComponentDto branch = db.components().insertProjectBranch(mainBranch);
188 SnapshotDto pastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
190 .setPeriodMode("last_version")
191 .setPeriodParam("2015-12-07")
192 .setPeriodDate(956789123987L));
193 SnapshotDto lastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
195 .setPeriodMode("last_version")
196 .setPeriodParam("2016-12-07")
197 .setPeriodDate(1_500L));
198 MetricDto gateDetailsMetric = insertGateDetailMetric();
199 dbClient.measureDao().insert(dbSession,
200 newMeasureDto(gateDetailsMetric, branch, pastAnalysis)
201 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
202 dbClient.measureDao().insert(dbSession,
203 newMeasureDto(gateDetailsMetric, branch, lastAnalysis)
204 .setData("not_used"));
206 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
208 String response = ws.newRequest()
209 .setParam(PARAM_ANALYSIS_ID, pastAnalysis.getUuid())
210 .execute().getInput();
212 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
216 public void return_live_status_when_project_is_referenced_by_its_key() throws IOException {
217 ProjectData projectData = db.components().insertPrivateProject();
218 ComponentDto project = projectData.getMainBranchComponent();
219 dbClient.snapshotDao().insert(dbSession, newAnalysis(project)
220 .setPeriodMode("last_version")
221 .setPeriodParam("2015-12-07")
222 .setPeriodDate(956789123987L));
223 MetricDto gateDetailsMetric = insertGateDetailMetric();
224 dbClient.liveMeasureDao().insert(dbSession,
225 newLiveMeasure(project, gateDetailsMetric)
226 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
228 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
230 String response = ws.newRequest()
231 .setParam(PARAM_PROJECT_KEY, project.getKey())
232 .execute().getInput();
234 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
238 public void return_live_status_when_branch_is_referenced_by_its_key() throws IOException {
239 ProjectData projectData = db.components().insertPrivateProject();
240 ComponentDto mainBranch = projectData.getMainBranchComponent();
241 String branchName = randomAlphanumeric(248);
242 ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setKey(branchName));
244 dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
245 .setPeriodMode("last_version")
246 .setPeriodParam("2015-12-07")
247 .setPeriodDate(956789123987L));
248 MetricDto gateDetailsMetric = insertGateDetailMetric();
249 dbClient.liveMeasureDao().insert(dbSession,
250 newLiveMeasure(branch, gateDetailsMetric)
251 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
253 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
255 String response = ws.newRequest()
256 .setParam(PARAM_PROJECT_KEY, mainBranch.getKey())
257 .setParam(PARAM_BRANCH, branchName)
258 .execute().getInput();
260 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
264 public void return_live_status_when_pull_request_is_referenced_by_its_key() throws IOException {
265 ProjectData projectData = db.components().insertPrivateProject();
266 ComponentDto mainBranch = projectData.getMainBranchComponent();
267 String pullRequestKey = RandomStringUtils.randomAlphanumeric(100);
268 ComponentDto pr = db.components().insertProjectBranch(mainBranch, branch -> branch.setBranchType(BranchType.PULL_REQUEST)
269 .setKey(pullRequestKey));
271 dbClient.snapshotDao().insert(dbSession, newAnalysis(pr)
272 .setPeriodMode("last_version")
273 .setPeriodParam("2015-12-07")
274 .setPeriodDate(956789123987L));
275 MetricDto gateDetailsMetric = insertGateDetailMetric();
276 dbClient.liveMeasureDao().insert(dbSession,
277 newLiveMeasure(pr, gateDetailsMetric)
278 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"))));
280 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
282 String response = ws.newRequest()
283 .setParam(PARAM_PROJECT_KEY, mainBranch.getKey())
284 .setParam(PARAM_PULL_REQUEST, pullRequestKey)
285 .execute().getInput();
287 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
291 public void return_undefined_status_if_specified_analysis_is_not_found() {
292 ProjectData projectData = db.components().insertPrivateProject();
293 ComponentDto mainBranch = projectData.getMainBranchComponent();
294 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
296 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
298 ProjectStatusResponse result = ws.newRequest()
299 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
300 .executeProtobuf(ProjectStatusResponse.class);
302 assertThat(result.getProjectStatus().getStatus()).isEqualTo(Status.NONE);
303 assertThat(result.getProjectStatus().getConditionsCount()).isZero();
307 public void return_undefined_status_if_project_is_not_analyzed() {
308 ProjectData projectData = db.components().insertPrivateProject();
309 ComponentDto mainBranch = projectData.getMainBranchComponent();
310 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
312 ProjectStatusResponse result = ws.newRequest()
313 .setParam(PARAM_PROJECT_ID, projectData.projectUuid())
314 .executeProtobuf(ProjectStatusResponse.class);
316 assertThat(result.getProjectStatus().getStatus()).isEqualTo(Status.NONE);
317 assertThat(result.getProjectStatus().getConditionsCount()).isZero();
321 public void project_administrator_is_allowed_to_get_project_status() {
322 ProjectData projectData = db.components().insertPrivateProject();
323 ComponentDto mainBranch = projectData.getMainBranchComponent();
324 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
326 userSession.addProjectPermission(UserRole.ADMIN, projectData.getProjectDto());
329 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
330 .executeProtobuf(ProjectStatusResponse.class);
334 public void project_user_is_allowed_to_get_project_status() {
335 ProjectData projectData = db.components().insertPrivateProject();
336 ComponentDto mainBranch = projectData.getMainBranchComponent();
337 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
339 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
342 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
343 .executeProtobuf(ProjectStatusResponse.class);
347 public void check_cayc_compliant_flag() {
348 ProjectData projectData = db.components().insertPrivateProject();
349 ComponentDto mainBranch = projectData.getMainBranchComponent();
350 var qg = db.qualityGates().insertBuiltInQualityGate();
351 db.qualityGates().setDefaultQualityGate(qg);
352 when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(projectData.projectUuid()))).thenReturn(COMPLIANT);
353 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
355 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
357 ProjectStatusResponse result = ws.newRequest()
358 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
359 .executeProtobuf(ProjectStatusResponse.class);
361 assertEquals(COMPLIANT.toString(), result.getProjectStatus().getCaycStatus());
365 public void user_with_project_scan_permission_is_allowed_to_get_project_status() {
366 ProjectData projectData = db.components().insertPrivateProject();
367 ComponentDto mainBranch = projectData.getMainBranchComponent();
368 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
370 userSession.addProjectPermission(UserRole.SCAN, projectData.getProjectDto());
372 var response = ws.newRequest()
373 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()).execute();
375 assertThat(response.getStatus()).isEqualTo(200);
379 public void user_with_global_scan_permission_is_allowed_to_get_project_status() {
380 ProjectData projectData = db.components().insertPrivateProject();
381 ComponentDto mainBranch = projectData.getMainBranchComponent();
382 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
384 userSession.addPermission(GlobalPermission.SCAN);
386 var response = ws.newRequest()
387 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()).execute();
389 assertThat(response.getStatus()).isEqualTo(200);
393 public void fail_if_no_snapshot_id_found() {
394 logInAsSystemAdministrator();
396 assertThatThrownBy(() -> ws.newRequest()
397 .setParam(PARAM_ANALYSIS_ID, ANALYSIS_ID)
398 .executeProtobuf(ProjectStatusResponse.class))
399 .isInstanceOf(NotFoundException.class)
400 .hasMessageContaining("Analysis with id 'task-uuid' is not found");
404 public void fail_if_insufficient_privileges() {
405 ProjectData projectData = db.components().insertPrivateProject();
406 ComponentDto mainBranch = projectData.getMainBranchComponent();
407 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
411 assertThatThrownBy(() -> ws.newRequest()
412 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
413 .executeProtobuf(ProjectStatusResponse.class))
414 .isInstanceOf(ForbiddenException.class);
418 public void fail_if_project_id_and_ce_task_id_provided() {
419 ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
420 logInAsSystemAdministrator();
422 assertThatThrownBy(() -> ws.newRequest()
423 .setParam(PARAM_ANALYSIS_ID, "analysis-id")
424 .setParam(PARAM_PROJECT_ID, "project-uuid")
425 .execute().getInput())
426 .isInstanceOf(BadRequestException.class)
427 .hasMessageContaining("Either 'analysisId', 'projectId' or 'projectKey' must be provided");
431 public void fail_if_branch_key_and_pull_request_id_provided() {
432 ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
433 logInAsSystemAdministrator();
435 assertThatThrownBy(() -> ws.newRequest()
436 .setParam(PARAM_PROJECT_KEY, "key")
437 .setParam(PARAM_BRANCH, "branch")
438 .setParam(PARAM_PULL_REQUEST, "pr")
439 .execute().getInput())
440 .isInstanceOf(BadRequestException.class)
441 .hasMessageContaining("Either 'branch' or 'pullRequest' can be provided, not both");
445 public void fail_if_no_parameter_provided() {
446 logInAsSystemAdministrator();
448 assertThatThrownBy(() -> ws.newRequest()
449 .execute().getInput())
450 .isInstanceOf(BadRequestException.class)
451 .hasMessageContaining("Either 'analysisId', 'projectId' or 'projectKey' must be provided");
455 public void fail_when_using_branch_uuid() {
456 ProjectData projectData = db.components().insertPublicProject();
457 ComponentDto mainBranch = projectData.getMainBranchComponent();
458 userSession.logIn().addProjectPermission(UserRole.ADMIN, projectData.getProjectDto());
459 ComponentDto branch = db.components().insertProjectBranch(mainBranch);
460 SnapshotDto snapshot = db.components().insertSnapshot(branch);
462 assertThatThrownBy(() -> ws.newRequest()
463 .setParam("projectId", branch.uuid())
465 .isInstanceOf(NotFoundException.class)
466 .hasMessageContaining(format("Project '%s' not found", branch.uuid()));
469 private void logInAsSystemAdministrator() {
470 userSession.logIn().setSystemAdministrator();
473 private MetricDto insertGateDetailMetric() {
474 return dbClient.metricDao().insert(dbSession, newMetricDto()
476 .setKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY));