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.ce.task.projectanalysis.issue;
22 import java.util.Arrays;
23 import javax.annotation.Nullable;
24 import org.junit.Before;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.slf4j.event.Level;
28 import org.sonar.api.testfixtures.log.LogTester;
29 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
30 import org.sonar.ce.task.projectanalysis.component.Component;
31 import org.sonar.ce.task.projectanalysis.scm.Changeset;
32 import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepositoryRule;
33 import org.sonar.core.issue.DefaultIssue;
34 import org.sonar.db.protobuf.DbCommons;
35 import org.sonar.db.protobuf.DbIssues;
36 import org.sonar.db.user.UserIdDto;
37 import org.sonar.server.issue.IssueFieldsSetter;
39 import static java.util.stream.Collectors.joining;
40 import static java.util.stream.IntStream.range;
41 import static org.assertj.core.api.Assertions.assertThat;
42 import static org.mockito.Mockito.mock;
43 import static org.mockito.Mockito.when;
44 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
46 public class IssueAssignerTest {
48 private static final int FILE_REF = 1;
49 private static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
52 public LogTester logTester = new LogTester();
55 public ScmInfoRepositoryRule scmInfoRepository = new ScmInfoRepositoryRule();
58 public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(123456789L);
60 private final ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
61 private final DefaultAssignee defaultAssignee = mock(DefaultAssignee.class);
62 private final IssueAssigner underTest = new IssueAssigner(analysisMetadataHolder, scmInfoRepository, scmAccountToUser, defaultAssignee, new IssueFieldsSetter());
65 public void before() {
66 logTester.setLevel(Level.DEBUG);
70 public void do_not_set_author_if_no_changeset() {
71 DefaultIssue issue = newIssueOnLines(1);
73 underTest.onIssue(FILE, issue);
75 assertThat(issue.authorLogin()).isNull();
79 public void set_author_of_new_issue_if_changeset() {
80 setSingleChangeset("john", 123456789L, "rev-1");
81 DefaultIssue issue = newIssueOnLines(1);
83 underTest.onIssue(FILE, issue);
85 assertThat(issue.authorLogin()).isEqualTo("john");
89 public void do_not_reset_author_if_already_set() {
90 setSingleChangeset("john", 123456789L, "rev-1");
91 DefaultIssue issue = newIssueOnLines(1)
92 .setAuthorLogin("jane");
94 underTest.onIssue(FILE, issue);
96 assertThat(issue.authorLogin()).isEqualTo("jane");
100 public void assign_but_do_not_set_author_if_too_long() {
101 String scmAuthor = range(0, 256).mapToObj(i -> "s").collect(joining());
102 addScmUser(scmAuthor, buildUserId("u123", "John C"));
103 setSingleChangeset(scmAuthor, 123456789L, "rev-1");
104 DefaultIssue issue = newIssueOnLines(1);
106 underTest.onIssue(FILE, issue);
108 assertThat(issue.authorLogin()).isNull();
109 assertThat(issue.assignee()).isEqualTo("u123");
110 assertThat(issue.assigneeLogin()).isEqualTo("John C");
112 assertThat(logTester.logs(Level.DEBUG)).contains("SCM account '" + scmAuthor + "' is too long to be stored as issue author");
116 public void assign_new_issue_to_author_of_change() {
117 addScmUser("john", buildUserId("u123", "john"));
118 setSingleChangeset("john", 123456789L, "rev-1");
119 DefaultIssue issue = newIssueOnLines(1);
121 underTest.onIssue(FILE, issue);
123 assertThat(issue.assignee()).isEqualTo("u123");
124 assertThat(issue.assigneeLogin()).isEqualTo("john");
128 public void assign_new_issue_to_default_assignee_if_author_not_found() {
129 setSingleChangeset("john", 123456789L, "rev-1");
130 when(defaultAssignee.loadDefaultAssigneeUserId()).thenReturn(new UserIdDto("u1234", "john"));
131 DefaultIssue issue = newIssueOnLines(1);
133 underTest.onIssue(FILE, issue);
135 assertThat(issue.assignee()).isEqualTo("u1234");
136 assertThat(issue.assigneeLogin()).isEqualTo("john");
140 public void do_not_assign_new_issue_if_no_author_in_changeset() {
141 setSingleChangeset(null, 123456789L, "rev-1");
142 DefaultIssue issue = newIssueOnLines(1);
144 underTest.onIssue(FILE, issue);
146 assertThat(issue.authorLogin()).isNull();
147 assertThat(issue.assignee()).isNull();
151 public void do_not_assign_issue_if_unassigned_but_already_authored() {
152 addScmUser("john", buildUserId("u1234", "john"));
153 setSingleChangeset("john", 123456789L, "rev-1");
154 DefaultIssue issue = newIssueOnLines(1)
155 .setAuthorLogin("john")
156 .setAssigneeUuid(null);
158 underTest.onIssue(FILE, issue);
160 assertThat(issue.authorLogin()).isEqualTo("john");
161 assertThat(issue.assignee()).isNull();
165 public void assign_to_last_committer_of_file_if_issue_is_global_to_file() {
166 addScmUser("henry", buildUserId("u123", "Henry V"));
167 Changeset changeset1 = Changeset.newChangesetBuilder()
170 .setRevision("rev-1")
173 Changeset changeset2 = Changeset.newChangesetBuilder()
176 .setRevision("rev-2")
178 scmInfoRepository.setScmInfo(FILE_REF, changeset1, changeset2, changeset1);
180 DefaultIssue issue = newIssueOnLines();
182 underTest.onIssue(FILE, issue);
184 assertThat(issue.assignee()).isEqualTo("u123");
185 assertThat(issue.assigneeLogin()).isEqualTo("Henry V");
189 public void assign_to_default_assignee_if_no_author() {
190 DefaultIssue issue = newIssueOnLines();
192 when(defaultAssignee.loadDefaultAssigneeUserId()).thenReturn(new UserIdDto("u123", "john"));
193 underTest.onIssue(FILE, issue);
195 assertThat(issue.assignee()).isEqualTo("u123");
196 assertThat(issue.assigneeLogin()).isEqualTo("john");
200 public void assign_to_default_assignee_if_no_scm_on_issue_locations() {
201 addScmUser("john", buildUserId("u123", "John C"));
202 Changeset changeset = Changeset.newChangesetBuilder()
205 .setRevision("rev-1")
207 scmInfoRepository.setScmInfo(FILE_REF, changeset, changeset);
208 DefaultIssue issue = newIssueOnLines(3);
210 underTest.onIssue(FILE, issue);
212 assertThat(issue.assignee()).isEqualTo("u123");
213 assertThat(issue.assigneeLogin()).isEqualTo("John C");
217 public void assign_to_author_of_the_most_recent_change_in_all_issue_locations() {
218 addScmUser("john", buildUserId("u1", "John"));
219 addScmUser("jane", buildUserId("u2", "Jane"));
220 Changeset commit1 = Changeset.newChangesetBuilder()
223 .setRevision("rev-1")
225 Changeset commit2 = Changeset.newChangesetBuilder()
228 .setRevision("rev-2")
230 scmInfoRepository.setScmInfo(FILE_REF, commit1, commit1, commit2, commit1);
231 DefaultIssue issue = newIssueOnLines(2, 3, 4);
233 underTest.onIssue(FILE, issue);
235 assertThat(issue.authorLogin()).isEqualTo("jane");
236 assertThat(issue.assignee()).isEqualTo("u2");
237 assertThat(issue.assigneeLogin()).isEqualTo("Jane");
240 private void setSingleChangeset(@Nullable String author, Long date, String revision) {
241 scmInfoRepository.setScmInfo(FILE_REF,
242 Changeset.newChangesetBuilder()
245 .setRevision(revision)
249 private void addScmUser(String scmAccount, UserIdDto userId) {
250 when(scmAccountToUser.getNullable(scmAccount)).thenReturn(userId);
253 private static DefaultIssue newIssueOnLines(int... lines) {
254 DefaultIssue issue = new DefaultIssue();
255 issue.setComponentUuid(FILE.getUuid());
256 DbIssues.Locations.Builder locations = DbIssues.Locations.newBuilder();
257 DbIssues.Flow.Builder flow = DbIssues.Flow.newBuilder();
258 Arrays.stream(lines).forEach(line -> flow.addLocation(newLocation(line)));
259 locations.addFlow(flow.build());
260 issue.setLocations(locations.build());
264 private static DbIssues.Location newLocation(int line) {
265 return DbIssues.Location.newBuilder()
266 .setComponentId(FILE.getUuid())
267 .setTextRange(DbCommons.TextRange.newBuilder().setStartLine(line).setEndLine(line).build()).build();
270 private UserIdDto buildUserId(String uuid, String login) {
271 return new UserIdDto(uuid, login);