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 org.apache.commons.io.IOUtils;
24 import org.apache.commons.lang3.RandomStringUtils;
25 import org.junit.Before;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.sonar.api.measures.CoreMetrics;
29 import org.sonar.api.server.ws.WebService;
30 import org.sonar.api.utils.System2;
31 import org.sonar.api.web.UserRole;
32 import org.sonar.db.DbClient;
33 import org.sonar.db.DbSession;
34 import org.sonar.db.DbTester;
35 import org.sonar.db.component.BranchType;
36 import org.sonar.db.component.ComponentDto;
37 import org.sonar.db.component.ProjectData;
38 import org.sonar.db.component.SnapshotDto;
39 import org.sonar.db.metric.MetricDto;
40 import org.sonar.db.permission.GlobalPermission;
41 import org.sonar.server.component.TestComponentFinder;
42 import org.sonar.server.exceptions.BadRequestException;
43 import org.sonar.server.exceptions.ForbiddenException;
44 import org.sonar.server.exceptions.NotFoundException;
45 import org.sonar.server.qualitygate.QualityGateCaycChecker;
46 import org.sonar.server.tester.UserSessionRule;
47 import org.sonar.server.ws.WsActionTester;
48 import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
49 import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status;
51 import static java.lang.String.format;
52 import static java.nio.charset.StandardCharsets.UTF_8;
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.newMeasure;
64 import static org.sonar.db.measure.MeasureTesting.newProjectMeasureDto;
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.projectMeasureDao().insert(dbSession,
119 newProjectMeasureDto(gateDetailsMetric, mainBranch, snapshot)
120 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), 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.projectMeasureDao().insert(dbSession,
146 newProjectMeasureDto(gateDetailsMetric, mainBranch, pastAnalysis)
147 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
148 dbClient.projectMeasureDao().insert(dbSession,
149 newProjectMeasureDto(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.measureDao().insert(dbSession,
171 newMeasure(mainBranch, gateDetailsMetric, IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
173 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
175 String response = ws.newRequest()
176 .setParam(PARAM_PROJECT_ID, projectData.projectUuid())
177 .execute().getInput();
179 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
183 public void return_past_status_when_branch_is_referenced_by_past_analysis_id() throws IOException {
184 ProjectData projectData = db.components().insertPrivateProject();
185 ComponentDto mainBranch = projectData.getMainBranchComponent();
186 ComponentDto branch = db.components().insertProjectBranch(mainBranch);
187 SnapshotDto pastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
189 .setPeriodMode("last_version")
190 .setPeriodParam("2015-12-07")
191 .setPeriodDate(956789123987L));
192 SnapshotDto lastAnalysis = dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
194 .setPeriodMode("last_version")
195 .setPeriodParam("2016-12-07")
196 .setPeriodDate(1_500L));
197 MetricDto gateDetailsMetric = insertGateDetailMetric();
198 dbClient.projectMeasureDao().insert(dbSession,
199 newProjectMeasureDto(gateDetailsMetric, branch, pastAnalysis)
200 .setData(IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
201 dbClient.projectMeasureDao().insert(dbSession,
202 newProjectMeasureDto(gateDetailsMetric, branch, lastAnalysis)
203 .setData("not_used"));
205 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
207 String response = ws.newRequest()
208 .setParam(PARAM_ANALYSIS_ID, pastAnalysis.getUuid())
209 .execute().getInput();
211 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
215 public void return_live_status_when_project_is_referenced_by_its_key() throws IOException {
216 ProjectData projectData = db.components().insertPrivateProject();
217 ComponentDto project = projectData.getMainBranchComponent();
218 dbClient.snapshotDao().insert(dbSession, newAnalysis(project)
219 .setPeriodMode("last_version")
220 .setPeriodParam("2015-12-07")
221 .setPeriodDate(956789123987L));
222 MetricDto gateDetailsMetric = insertGateDetailMetric();
223 dbClient.measureDao().insert(dbSession,
224 newMeasure(project, gateDetailsMetric, IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
226 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
228 String response = ws.newRequest()
229 .setParam(PARAM_PROJECT_KEY, project.getKey())
230 .execute().getInput();
232 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
236 public void return_live_status_when_branch_is_referenced_by_its_key() throws IOException {
237 ProjectData projectData = db.components().insertPrivateProject();
238 ComponentDto mainBranch = projectData.getMainBranchComponent();
239 String branchName = randomAlphanumeric(248);
240 ComponentDto branch = db.components().insertProjectBranch(mainBranch, b -> b.setKey(branchName));
242 dbClient.snapshotDao().insert(dbSession, newAnalysis(branch)
243 .setPeriodMode("last_version")
244 .setPeriodParam("2015-12-07")
245 .setPeriodDate(956789123987L));
246 MetricDto gateDetailsMetric = insertGateDetailMetric();
247 dbClient.measureDao().insert(dbSession,
248 newMeasure(branch, gateDetailsMetric, IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
250 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
252 String response = ws.newRequest()
253 .setParam(PARAM_PROJECT_KEY, mainBranch.getKey())
254 .setParam(PARAM_BRANCH, branchName)
255 .execute().getInput();
257 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
261 public void return_live_status_when_pull_request_is_referenced_by_its_key() throws IOException {
262 ProjectData projectData = db.components().insertPrivateProject();
263 ComponentDto mainBranch = projectData.getMainBranchComponent();
264 String pullRequestKey = RandomStringUtils.randomAlphanumeric(100);
265 ComponentDto pr = db.components().insertProjectBranch(mainBranch, branch -> branch.setBranchType(BranchType.PULL_REQUEST)
266 .setKey(pullRequestKey));
268 dbClient.snapshotDao().insert(dbSession, newAnalysis(pr)
269 .setPeriodMode("last_version")
270 .setPeriodParam("2015-12-07")
271 .setPeriodDate(956789123987L));
272 MetricDto gateDetailsMetric = insertGateDetailMetric();
273 dbClient.measureDao().insert(dbSession,
274 newMeasure(pr, gateDetailsMetric, IOUtils.toString(getClass().getResource("ProjectStatusActionIT/measure_data.json"), UTF_8)));
276 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
278 String response = ws.newRequest()
279 .setParam(PARAM_PROJECT_KEY, mainBranch.getKey())
280 .setParam(PARAM_PULL_REQUEST, pullRequestKey)
281 .execute().getInput();
283 assertJson(response).isSimilarTo(getClass().getResource("project_status-example.json"));
287 public void return_undefined_status_if_specified_analysis_is_not_found() {
288 ProjectData projectData = db.components().insertPrivateProject();
289 ComponentDto mainBranch = projectData.getMainBranchComponent();
290 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
292 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
294 ProjectStatusResponse result = ws.newRequest()
295 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
296 .executeProtobuf(ProjectStatusResponse.class);
298 assertThat(result.getProjectStatus().getStatus()).isEqualTo(Status.NONE);
299 assertThat(result.getProjectStatus().getConditionsCount()).isZero();
303 public void return_undefined_status_if_project_is_not_analyzed() {
304 ProjectData projectData = db.components().insertPrivateProject();
305 ComponentDto mainBranch = projectData.getMainBranchComponent();
306 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
308 ProjectStatusResponse result = ws.newRequest()
309 .setParam(PARAM_PROJECT_ID, projectData.projectUuid())
310 .executeProtobuf(ProjectStatusResponse.class);
312 assertThat(result.getProjectStatus().getStatus()).isEqualTo(Status.NONE);
313 assertThat(result.getProjectStatus().getConditionsCount()).isZero();
317 public void project_administrator_is_allowed_to_get_project_status() {
318 ProjectData projectData = db.components().insertPrivateProject();
319 ComponentDto mainBranch = projectData.getMainBranchComponent();
320 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
322 userSession.addProjectPermission(UserRole.ADMIN, projectData.getProjectDto());
325 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
326 .executeProtobuf(ProjectStatusResponse.class);
330 public void project_user_is_allowed_to_get_project_status() {
331 ProjectData projectData = db.components().insertPrivateProject();
332 ComponentDto mainBranch = projectData.getMainBranchComponent();
333 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
335 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
338 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
339 .executeProtobuf(ProjectStatusResponse.class);
343 public void check_cayc_compliant_flag() {
344 ProjectData projectData = db.components().insertPrivateProject();
345 ComponentDto mainBranch = projectData.getMainBranchComponent();
346 var qg = db.qualityGates().insertBuiltInQualityGate();
347 db.qualityGates().setDefaultQualityGate(qg);
348 when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(projectData.projectUuid()))).thenReturn(COMPLIANT);
349 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
351 userSession.addProjectPermission(UserRole.USER, projectData.getProjectDto());
353 ProjectStatusResponse result = ws.newRequest()
354 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
355 .executeProtobuf(ProjectStatusResponse.class);
357 assertEquals(COMPLIANT.toString(), result.getProjectStatus().getCaycStatus());
361 public void user_with_project_scan_permission_is_allowed_to_get_project_status() {
362 ProjectData projectData = db.components().insertPrivateProject();
363 ComponentDto mainBranch = projectData.getMainBranchComponent();
364 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
366 userSession.addProjectPermission(UserRole.SCAN, projectData.getProjectDto());
368 var response = ws.newRequest()
369 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()).execute();
371 assertThat(response.getStatus()).isEqualTo(200);
375 public void user_with_global_scan_permission_is_allowed_to_get_project_status() {
376 ProjectData projectData = db.components().insertPrivateProject();
377 ComponentDto mainBranch = projectData.getMainBranchComponent();
378 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
380 userSession.addPermission(GlobalPermission.SCAN);
382 var response = ws.newRequest()
383 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid()).execute();
385 assertThat(response.getStatus()).isEqualTo(200);
389 public void fail_if_no_snapshot_id_found() {
390 logInAsSystemAdministrator();
392 assertThatThrownBy(() -> ws.newRequest()
393 .setParam(PARAM_ANALYSIS_ID, ANALYSIS_ID)
394 .executeProtobuf(ProjectStatusResponse.class))
395 .isInstanceOf(NotFoundException.class)
396 .hasMessageContaining("Analysis with id 'task-uuid' is not found");
400 public void fail_if_insufficient_privileges() {
401 ProjectData projectData = db.components().insertPrivateProject();
402 ComponentDto mainBranch = projectData.getMainBranchComponent();
403 SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(mainBranch));
407 assertThatThrownBy(() -> ws.newRequest()
408 .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
409 .executeProtobuf(ProjectStatusResponse.class))
410 .isInstanceOf(ForbiddenException.class);
414 public void fail_if_project_id_and_ce_task_id_provided() {
415 ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
416 logInAsSystemAdministrator();
418 assertThatThrownBy(() -> ws.newRequest()
419 .setParam(PARAM_ANALYSIS_ID, "analysis-id")
420 .setParam(PARAM_PROJECT_ID, "project-uuid")
421 .execute().getInput())
422 .isInstanceOf(BadRequestException.class)
423 .hasMessageContaining("Either 'analysisId', 'projectId' or 'projectKey' must be provided");
427 public void fail_if_branch_key_and_pull_request_id_provided() {
428 ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
429 logInAsSystemAdministrator();
431 assertThatThrownBy(() -> ws.newRequest()
432 .setParam(PARAM_PROJECT_KEY, "key")
433 .setParam(PARAM_BRANCH, "branch")
434 .setParam(PARAM_PULL_REQUEST, "pr")
435 .execute().getInput())
436 .isInstanceOf(BadRequestException.class)
437 .hasMessageContaining("Either 'branch' or 'pullRequest' can be provided, not both");
441 public void fail_if_no_parameter_provided() {
442 logInAsSystemAdministrator();
444 assertThatThrownBy(() -> ws.newRequest()
445 .execute().getInput())
446 .isInstanceOf(BadRequestException.class)
447 .hasMessageContaining("Either 'analysisId', 'projectId' or 'projectKey' must be provided");
451 public void fail_when_using_branch_uuid() {
452 ProjectData projectData = db.components().insertPublicProject();
453 ComponentDto mainBranch = projectData.getMainBranchComponent();
454 userSession.logIn().addProjectPermission(UserRole.ADMIN, projectData.getProjectDto());
455 ComponentDto branch = db.components().insertProjectBranch(mainBranch);
456 SnapshotDto snapshot = db.components().insertSnapshot(branch);
458 assertThatThrownBy(() -> ws.newRequest()
459 .setParam("projectId", branch.uuid())
461 .isInstanceOf(NotFoundException.class)
462 .hasMessageContaining(format("Project '%s' not found", branch.uuid()));
465 private void logInAsSystemAdministrator() {
466 userSession.logIn().setSystemAdministrator();
469 private MetricDto insertGateDetailMetric() {
470 return dbClient.metricDao().insert(dbSession, newMetricDto()
472 .setKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY));