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.issue.ws;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Date;
25 import java.util.List;
27 import org.junit.Before;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.mockito.junit.MockitoJUnitRunner;
32 import org.sonar.api.issue.IssueStatus;
33 import org.sonar.api.resources.Languages;
34 import org.sonar.api.rules.CleanCodeAttribute;
35 import org.sonar.api.utils.Duration;
36 import org.sonar.api.utils.Durations;
37 import org.sonar.db.DbTester;
38 import org.sonar.db.component.BranchDto;
39 import org.sonar.db.component.BranchType;
40 import org.sonar.db.component.ComponentDto;
41 import org.sonar.db.issue.IssueChangeDto;
42 import org.sonar.db.issue.IssueDto;
43 import org.sonar.db.project.ProjectDto;
44 import org.sonar.db.rule.RuleDto;
45 import org.sonar.db.user.UserDto;
46 import org.sonar.server.issue.TextRangeResponseFormatter;
47 import org.sonar.server.issue.workflow.Transition;
48 import org.sonarqube.ws.Common;
49 import org.sonarqube.ws.Issues.Issue;
50 import org.sonarqube.ws.Issues.Operation;
52 import static java.lang.System.currentTimeMillis;
53 import static java.util.stream.Collectors.toList;
54 import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
55 import static org.assertj.core.api.Assertions.assertThat;
56 import static org.assertj.core.groups.Tuple.tuple;
57 import static org.mockito.ArgumentMatchers.any;
58 import static org.mockito.ArgumentMatchers.eq;
59 import static org.mockito.Mockito.mock;
60 import static org.mockito.Mockito.when;
61 import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
62 import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
63 import static org.sonar.api.rules.RuleType.CODE_SMELL;
64 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
65 import static org.sonar.api.utils.DateUtils.formatDateTime;
66 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
67 import static org.sonar.db.issue.IssueTesting.newIssue;
68 import static org.sonar.db.issue.IssueTesting.newIssueChangeDto;
69 import static org.sonar.db.rule.RuleTesting.newRule;
70 import static org.sonar.db.user.UserTesting.newUserDto;
71 import static org.sonar.server.issue.index.IssueScope.MAIN;
72 import static org.sonar.server.issue.index.IssueScope.TEST;
74 @RunWith(MockitoJUnitRunner.class)
75 public class SearchResponseFormatFormatOperationTest {
77 public DbTester db = DbTester.create();
78 private final Durations durations = new Durations();
79 private final Languages languages = mock(Languages.class);
80 private final TextRangeResponseFormatter textRangeResponseFormatter = mock(TextRangeResponseFormatter.class);
81 private final UserResponseFormatter userResponseFormatter = mock(UserResponseFormatter.class);
82 private final Common.User user = mock(Common.User.class);
83 private final SearchResponseFormat searchResponseFormat = new SearchResponseFormat(durations, languages, textRangeResponseFormatter, userResponseFormatter);
85 private SearchResponseData searchResponseData;
86 private IssueDto issueDto;
87 private ComponentDto componentDto;
88 private UserDto userDto;
92 searchResponseData = newSearchResponseDataMainBranch();
96 public void formatOperation_should_add_components_to_response() {
97 Operation result = searchResponseFormat.formatOperation(searchResponseData);
99 assertThat(result.getComponentsList()).hasSize(1);
100 assertThat(result.getComponentsList().get(0).getKey()).isEqualTo(issueDto.getComponentKey());
104 public void formatOperation_should_add_rules_to_response() {
105 Operation result = searchResponseFormat.formatOperation(searchResponseData);
107 assertThat(result.getRulesList()).hasSize(1);
108 assertThat(result.getRulesList().get(0).getKey()).isEqualTo(issueDto.getRuleKey().toString());
112 public void formatOperation_should_add_users_to_response() {
113 Operation result = searchResponseFormat.formatOperation(searchResponseData);
115 assertThat(result.getUsersList()).hasSize(1);
116 assertThat(result.getUsers(0)).isSameAs(user);
120 public void formatOperation_should_add_issue_to_response() {
121 Operation result = searchResponseFormat.formatOperation(searchResponseData);
123 assertIssueEqualsIssueDto(result.getIssue(), issueDto);
126 private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
127 assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
128 assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getEffectiveCleanCodeAttribute().name()));
129 assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getEffectiveCleanCodeAttribute().getAttributeCategory().name()));
130 assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
131 assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
132 assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
133 assertThat(issue.getSeverity()).hasToString(issueDto.getSeverity());
134 assertThat(issue.getAssignee()).isEqualTo(userDto.getLogin());
135 assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
136 assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
137 assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
138 assertThat(new ArrayList<>(issue.getTagsList())).containsExactlyInAnyOrderElementsOf(issueDto.getTags());
139 assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
140 assertThat(issue.getHash()).isEqualTo(issueDto.getChecksum());
141 assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
142 assertThat(issue.getCreationDate()).isEqualTo(formatDateTime(issueDto.getIssueCreationDate()));
143 assertThat(issue.getUpdateDate()).isEqualTo(formatDateTime(issueDto.getIssueUpdateDate()));
144 assertThat(issue.getCloseDate()).isEqualTo(formatDateTime(issueDto.getIssueCloseDate()));
145 assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
146 assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
147 assertThat(new ArrayList<>(issue.getCodeVariantsList())).containsExactlyInAnyOrderElementsOf(issueDto.getCodeVariants());
148 assertThat(issue.getImpactsList())
149 .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
150 .containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
153 .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
158 public void formatOperation_should_not_add_issue_when_several_issue() {
159 searchResponseData = new SearchResponseData(List.of(createIssue(), createIssue()));
161 Operation result = searchResponseFormat.formatOperation(searchResponseData);
163 assertThat(result.getIssue()).isEqualTo(Issue.getDefaultInstance());
166 private static IssueDto createIssue() {
167 RuleDto ruleDto = newRule();
168 String projectUuid = "project_uuid_" + randomAlphanumeric(5);
169 ComponentDto projectDto = newPrivateProjectDto();
170 projectDto.setBranchUuid(projectUuid);
171 return newIssue(ruleDto, projectUuid, "project_key_" + randomAlphanumeric(5), projectDto);
175 public void formatOperation_should_add_branch_on_issue() {
176 String branchName = randomAlphanumeric(5);
177 searchResponseData = newSearchResponseDataBranch(branchName);
178 Operation result = searchResponseFormat.formatOperation(searchResponseData);
179 assertThat(result.getIssue().getBranch()).isEqualTo(branchName);
183 public void formatOperation_should_add_pullrequest_on_issue() {
184 searchResponseData = newSearchResponseDataPr("pr1");
185 Operation result = searchResponseFormat.formatOperation(searchResponseData);
186 assertThat(result.getIssue().getPullRequest()).isEqualTo("pr1");
190 public void formatOperation_should_add_project_on_issue() {
191 issueDto.setProjectUuid(componentDto.uuid());
193 Operation result = searchResponseFormat.formatOperation(searchResponseData);
195 assertThat(result.getIssue().getProject()).isEqualTo(componentDto.getKey());
199 public void formatOperation_should_add_external_rule_engine_on_issue() {
200 issueDto.setExternal(true);
201 String expected = randomAlphanumeric(5);
202 issueDto.setRuleKey(EXTERNAL_RULE_REPO_PREFIX + expected, randomAlphanumeric(5));
204 Operation result = searchResponseFormat.formatOperation(searchResponseData);
206 assertThat(result.getIssue().getExternalRuleEngine()).isEqualTo(expected);
210 public void formatOperation_should_add_effort_and_debt_on_issue() {
212 issueDto.setEffort(effort);
213 String expected = durations.encode(Duration.create(effort));
215 Operation result = searchResponseFormat.formatOperation(searchResponseData);
217 assertThat(result.getIssue().getEffort()).isEqualTo(expected);
218 assertThat(result.getIssue().getDebt()).isEqualTo(expected);
222 public void formatOperation_should_add_scope_test_on_issue_when_unit_test_file() {
223 componentDto.setQualifier(UNIT_TEST_FILE);
225 Operation result = searchResponseFormat.formatOperation(searchResponseData);
227 assertThat(result.getIssue().getScope()).isEqualTo(TEST.name());
231 public void formatOperation_should_add_scope_main_on_issue_when_not_unit_test_file() {
232 componentDto.setQualifier(randomAlphanumeric(5));
234 Operation result = searchResponseFormat.formatOperation(searchResponseData);
236 assertThat(result.getIssue().getScope()).isEqualTo(MAIN.name());
240 public void formatOperation_should_add_actions_on_issues() {
241 Set<String> expectedActions = Set.of("actionA", "actionB");
242 searchResponseData.addActions(issueDto.getKey(), expectedActions);
244 Operation result = searchResponseFormat.formatOperation(searchResponseData);
246 assertThat(result.getIssue().getActions().getActionsList()).containsExactlyInAnyOrderElementsOf(expectedActions);
250 public void formatOperation_should_add_transitions_on_issues() {
251 Set<String> expectedTransitions = Set.of("transitionone", "transitiontwo");
252 searchResponseData.addTransitions(issueDto.getKey(), createFakeTransitions(expectedTransitions));
254 Operation result = searchResponseFormat.formatOperation(searchResponseData);
256 assertThat(result.getIssue().getTransitions().getTransitionsList()).containsExactlyInAnyOrderElementsOf(expectedTransitions);
259 private static List<Transition> createFakeTransitions(Collection<String> transitions) {
260 return transitions.stream()
261 .map(transition -> Transition.builder(transition).from("OPEN").to("RESOLVED").build())
266 public void formatOperation_should_add_comments_on_issues() {
267 IssueChangeDto issueChangeDto = newIssueChangeDto(issueDto);
268 searchResponseData.setComments(List.of(issueChangeDto));
270 Operation result = searchResponseFormat.formatOperation(searchResponseData);
272 assertThat(result.getIssue().getComments().getCommentsList()).hasSize(1).extracting(Common.Comment::getKey).containsExactly(issueChangeDto.getKey());
276 public void formatOperation_should_not_set_severity_for_security_hotspot_issue() {
277 issueDto.setType(SECURITY_HOTSPOT);
279 Operation result = searchResponseFormat.formatOperation(searchResponseData);
281 assertThat(result.getIssue().hasSeverity()).isFalse();
285 public void formatOperation_shouldReturnExpectedIssueStatus() {
286 issueDto.setStatus(org.sonar.api.issue.Issue.STATUS_RESOLVED);
287 issueDto.setResolution(org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX);
289 Operation result = searchResponseFormat.formatOperation(searchResponseData);
291 assertThat(result.getIssue().getIssueStatus()).isEqualTo(IssueStatus.ACCEPTED.name());
294 private SearchResponseData newSearchResponseDataMainBranch() {
295 ComponentDto projectDto = db.components().insertPublicProject().getMainBranchComponent();
296 BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(db.getSession(), projectDto.uuid()).get();
297 return newSearchResponseData(projectDto, branchDto);
300 private SearchResponseData newSearchResponseDataBranch(String name) {
301 ProjectDto projectDto = db.components().insertPublicProject().getProjectDto();
302 BranchDto branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(name));
303 ComponentDto branchComponent = db.components().getComponentDto(branch);
304 return newSearchResponseData(branchComponent, branch);
307 private SearchResponseData newSearchResponseDataPr(String name) {
308 ProjectDto projectDto = db.components().insertPublicProject().getProjectDto();
309 BranchDto branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(name).setBranchType(BranchType.PULL_REQUEST));
310 ComponentDto branchComponent = db.components().getComponentDto(branch);
311 return newSearchResponseData(branchComponent, branch);
314 private SearchResponseData newSearchResponseData(ComponentDto component, BranchDto branch) {
315 RuleDto ruleDto = newRule();
316 userDto = newUserDto();
317 componentDto = component;
318 issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component)
320 .setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
321 .setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
322 .setAssigneeUuid(userDto.getUuid())
323 .setResolution("resolution_" + randomAlphanumeric(5))
324 .setIssueCreationDate(new Date(currentTimeMillis() - 2_000))
325 .setIssueUpdateDate(new Date(currentTimeMillis() - 1_000))
326 .setIssueCloseDate(new Date(currentTimeMillis()));
328 SearchResponseData searchResponseData = new SearchResponseData(issueDto);
329 searchResponseData.addComponents(List.of(component));
330 searchResponseData.addRules(List.of(ruleDto));
331 searchResponseData.addUsers(List.of(userDto));
332 searchResponseData.addBranches(List.of(branch));
334 when(userResponseFormatter.formatUser(any(Common.User.Builder.class), eq(userDto))).thenReturn(user);
336 return searchResponseData;