]> source.dussan.org Git - sonarqube.git/blob
422f1d870b3fd5330b2a64c81aaaeec4f1ac0968
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.server.issue.ws;
21
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Date;
25 import java.util.List;
26 import java.util.Set;
27 import org.junit.jupiter.api.BeforeEach;
28 import org.junit.jupiter.api.Test;
29 import org.junit.jupiter.api.extension.ExtendWith;
30 import org.junit.jupiter.api.extension.RegisterExtension;
31 import org.mockito.junit.jupiter.MockitoExtension;
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;
51
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;
73
74 @ExtendWith(MockitoExtension.class)
75 class SearchResponseFormatFormatOperationTest {
76   @RegisterExtension
77   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,
84     userResponseFormatter);
85
86   private SearchResponseData searchResponseData;
87   private IssueDto issueDto;
88   private ComponentDto componentDto;
89   private UserDto userDto;
90
91   @BeforeEach
92   void setUp() {
93     searchResponseData = newSearchResponseDataMainBranch();
94   }
95
96   @Test
97   void formatOperation_should_add_components_to_response() {
98     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
99
100     assertThat(result.getComponentsList()).hasSize(1);
101     assertThat(result.getComponentsList().get(0).getKey()).isEqualTo(issueDto.getComponentKey());
102   }
103
104   @Test
105   void formatOperation_should_add_rules_to_response() {
106     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
107
108     assertThat(result.getRulesList()).hasSize(1);
109     assertThat(result.getRulesList().get(0).getKey()).isEqualTo(issueDto.getRuleKey().toString());
110   }
111
112   @Test
113   void formatOperation_should_add_users_to_response() {
114     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
115
116     assertThat(result.getUsersList()).hasSize(1);
117     assertThat(result.getUsers(0)).isSameAs(user);
118   }
119
120   @Test
121   void formatOperation_does_not_add_author_to_response_if_showAuthor_false() {
122     Operation result = searchResponseFormat.formatOperation(searchResponseData, false);
123
124     assertThat(result.getIssue().getAuthor()).isEmpty();
125   }
126
127   @Test
128   void formatOperation_should_add_issue_to_response() {
129     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
130
131     assertIssueEqualsIssueDto(result.getIssue(), issueDto);
132   }
133
134   private void assertIssueEqualsIssueDto(Issue issue, IssueDto issueDto) {
135     assertThat(issue.getKey()).isEqualTo(issueDto.getKey());
136     assertThat(issue.getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.valueOf(issueDto.getEffectiveCleanCodeAttribute().name()));
137     assertThat(issue.getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.valueOf(issueDto.getEffectiveCleanCodeAttribute().getAttributeCategory().name()));
138     assertThat(issue.getType().getNumber()).isEqualTo(issueDto.getType());
139     assertThat(issue.getComponent()).isEqualTo(issueDto.getComponentKey());
140     assertThat(issue.getRule()).isEqualTo(issueDto.getRuleKey().toString());
141     assertThat(issue.getSeverity()).hasToString(issueDto.getSeverity());
142     assertThat(issue.getAssignee()).isEqualTo(userDto.getLogin());
143     assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
144     assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
145     assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
146     assertThat(new ArrayList<>(issue.getTagsList())).containsExactlyInAnyOrderElementsOf(issueDto.getTags());
147     assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
148     assertThat(issue.getHash()).isEqualTo(issueDto.getChecksum());
149     assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
150     assertThat(issue.getCreationDate()).isEqualTo(formatDateTime(issueDto.getIssueCreationDate()));
151     assertThat(issue.getUpdateDate()).isEqualTo(formatDateTime(issueDto.getIssueUpdateDate()));
152     assertThat(issue.getCloseDate()).isEqualTo(formatDateTime(issueDto.getIssueCloseDate()));
153     assertThat(issue.getQuickFixAvailable()).isEqualTo(issueDto.isQuickFixAvailable());
154     assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issueDto.getOptionalRuleDescriptionContextKey().orElse(null));
155     assertThat(new ArrayList<>(issue.getCodeVariantsList())).containsExactlyInAnyOrderElementsOf(issueDto.getCodeVariants());
156     assertThat(issue.getImpactsList())
157       .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
158       .containsExactlyInAnyOrderElementsOf(issueDto.getEffectiveImpacts()
159         .entrySet()
160         .stream()
161         .map(entry -> tuple(Common.SoftwareQuality.valueOf(entry.getKey().name()), Common.ImpactSeverity.valueOf(entry.getValue().name())))
162         .collect(toList()));
163   }
164
165   @Test
166   void formatOperation_should_not_add_issue_when_several_issue() {
167     searchResponseData = new SearchResponseData(List.of(createIssue(), createIssue()));
168
169     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
170
171     assertThat(result.getIssue()).isEqualTo(Issue.getDefaultInstance());
172   }
173
174   private static IssueDto createIssue() {
175     RuleDto ruleDto = newRule();
176     String projectUuid = "project_uuid_" + randomAlphanumeric(5);
177     ComponentDto projectDto = newPrivateProjectDto();
178     projectDto.setBranchUuid(projectUuid);
179     return newIssue(ruleDto, projectUuid, "project_key_" + randomAlphanumeric(5), projectDto);
180   }
181
182   @Test
183   void formatOperation_should_add_branch_on_issue() {
184     String branchName = randomAlphanumeric(5);
185     searchResponseData = newSearchResponseDataBranch(branchName);
186     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
187     assertThat(result.getIssue().getBranch()).isEqualTo(branchName);
188   }
189
190   @Test
191   void formatOperation_should_add_pullrequest_on_issue() {
192     searchResponseData = newSearchResponseDataPr("pr1");
193     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
194     assertThat(result.getIssue().getPullRequest()).isEqualTo("pr1");
195   }
196
197   @Test
198   void formatOperation_should_add_project_on_issue() {
199     issueDto.setProjectUuid(componentDto.uuid());
200
201     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
202
203     assertThat(result.getIssue().getProject()).isEqualTo(componentDto.getKey());
204   }
205
206   @Test
207   void formatOperation_should_add_external_rule_engine_on_issue() {
208     issueDto.setExternal(true);
209     String expected = randomAlphanumeric(5);
210     issueDto.setRuleKey(EXTERNAL_RULE_REPO_PREFIX + expected, randomAlphanumeric(5));
211
212     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
213
214     assertThat(result.getIssue().getExternalRuleEngine()).isEqualTo(expected);
215   }
216
217   @Test
218   void formatOperation_should_add_effort_and_debt_on_issue() {
219     long effort = 60L;
220     issueDto.setEffort(effort);
221     String expected = durations.encode(Duration.create(effort));
222
223     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
224
225     assertThat(result.getIssue().getEffort()).isEqualTo(expected);
226     assertThat(result.getIssue().getDebt()).isEqualTo(expected);
227   }
228
229   @Test
230   void formatOperation_should_add_scope_test_on_issue_when_unit_test_file() {
231     componentDto.setQualifier(UNIT_TEST_FILE);
232
233     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
234
235     assertThat(result.getIssue().getScope()).isEqualTo(TEST.name());
236   }
237
238   @Test
239   void formatOperation_should_add_scope_main_on_issue_when_not_unit_test_file() {
240     componentDto.setQualifier(randomAlphanumeric(5));
241
242     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
243
244     assertThat(result.getIssue().getScope()).isEqualTo(MAIN.name());
245   }
246
247   @Test
248   void formatOperation_should_add_actions_on_issues() {
249     Set<String> expectedActions = Set.of("actionA", "actionB");
250     searchResponseData.addActions(issueDto.getKey(), expectedActions);
251
252     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
253
254     assertThat(result.getIssue().getActions().getActionsList()).containsExactlyInAnyOrderElementsOf(expectedActions);
255   }
256
257   @Test
258   void formatOperation_should_add_transitions_on_issues() {
259     Set<String> expectedTransitions = Set.of("transitionone", "transitiontwo");
260     searchResponseData.addTransitions(issueDto.getKey(), createFakeTransitions(expectedTransitions));
261
262     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
263
264     assertThat(result.getIssue().getTransitions().getTransitionsList()).containsExactlyInAnyOrderElementsOf(expectedTransitions);
265   }
266
267   private static List<Transition> createFakeTransitions(Collection<String> transitions) {
268     return transitions.stream()
269       .map(transition -> Transition.builder(transition).from("OPEN").to("RESOLVED").build())
270       .collect(toList());
271   }
272
273   @Test
274   void formatOperation_should_add_comments_on_issues() {
275     IssueChangeDto issueChangeDto = newIssueChangeDto(issueDto);
276     searchResponseData.setComments(List.of(issueChangeDto));
277
278     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
279
280     assertThat(result.getIssue().getComments().getCommentsList()).hasSize(1).extracting(Common.Comment::getKey).containsExactly(issueChangeDto.getKey());
281   }
282
283   @Test
284   void formatOperation_should_not_set_severity_for_security_hotspot_issue() {
285     issueDto.setType(SECURITY_HOTSPOT);
286
287     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
288
289     assertThat(result.getIssue().hasSeverity()).isFalse();
290   }
291
292   @Test
293   void formatOperation_shouldReturnExpectedIssueStatus() {
294     issueDto.setStatus(org.sonar.api.issue.Issue.STATUS_RESOLVED);
295     issueDto.setResolution(org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX);
296
297     Operation result = searchResponseFormat.formatOperation(searchResponseData, true);
298
299     assertThat(result.getIssue().getIssueStatus()).isEqualTo(IssueStatus.ACCEPTED.name());
300   }
301
302   private SearchResponseData newSearchResponseDataMainBranch() {
303     ComponentDto projectDto = db.components().insertPublicProject().getMainBranchComponent();
304     BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(db.getSession(), projectDto.uuid()).get();
305     return newSearchResponseData(projectDto, branchDto);
306   }
307
308   private SearchResponseData newSearchResponseDataBranch(String name) {
309     ProjectDto projectDto = db.components().insertPublicProject().getProjectDto();
310     BranchDto branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(name));
311     ComponentDto branchComponent = db.components().getComponentDto(branch);
312     return newSearchResponseData(branchComponent, branch);
313   }
314
315   private SearchResponseData newSearchResponseDataPr(String name) {
316     ProjectDto projectDto = db.components().insertPublicProject().getProjectDto();
317     BranchDto branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(name).setBranchType(BranchType.PULL_REQUEST));
318     ComponentDto branchComponent = db.components().getComponentDto(branch);
319     return newSearchResponseData(branchComponent, branch);
320   }
321
322   private SearchResponseData newSearchResponseData(ComponentDto component, BranchDto branch) {
323     RuleDto ruleDto = newRule();
324     userDto = newUserDto();
325     componentDto = component;
326     issueDto = newIssue(ruleDto, component.branchUuid(), component.getKey(), component)
327       .setType(CODE_SMELL)
328       .setCleanCodeAttribute(CleanCodeAttribute.CLEAR)
329       .setRuleDescriptionContextKey("context_key_" + randomAlphanumeric(5))
330       .setAssigneeUuid(userDto.getUuid())
331       .setResolution("resolution_" + randomAlphanumeric(5))
332       .setIssueCreationDate(new Date(currentTimeMillis() - 2_000))
333       .setIssueUpdateDate(new Date(currentTimeMillis() - 1_000))
334       .setIssueCloseDate(new Date(currentTimeMillis()));
335
336     SearchResponseData searchResponseData = new SearchResponseData(issueDto);
337     searchResponseData.addComponents(List.of(component));
338     searchResponseData.addRules(List.of(ruleDto));
339     searchResponseData.addUsers(List.of(userDto));
340     searchResponseData.addBranches(List.of(branch));
341
342     when(userResponseFormatter.formatUser(any(Common.User.Builder.class), eq(userDto))).thenReturn(user);
343
344     return searchResponseData;
345   }
346 }