選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

SendIssueNotificationsStepTest.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 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.ce.task.projectanalysis.step;
  21. import java.io.IOException;
  22. import java.util.Date;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Random;
  27. import java.util.stream.IntStream;
  28. import java.util.stream.Stream;
  29. import org.junit.Before;
  30. import org.junit.Rule;
  31. import org.junit.Test;
  32. import org.junit.rules.TemporaryFolder;
  33. import org.mockito.ArgumentCaptor;
  34. import org.sonar.api.rules.RuleType;
  35. import org.sonar.api.utils.Duration;
  36. import org.sonar.api.utils.System2;
  37. import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
  38. import org.sonar.ce.task.projectanalysis.analysis.Branch;
  39. import org.sonar.ce.task.projectanalysis.component.Component;
  40. import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
  41. import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
  42. import org.sonar.ce.task.projectanalysis.issue.IssueCache;
  43. import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryRule;
  44. import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
  45. import org.sonar.ce.task.step.ComputationStep;
  46. import org.sonar.ce.task.step.TestComputationStepContext;
  47. import org.sonar.core.issue.DefaultIssue;
  48. import org.sonar.db.DbTester;
  49. import org.sonar.db.component.ComponentDto;
  50. import org.sonar.db.rule.RuleDefinitionDto;
  51. import org.sonar.db.user.UserDto;
  52. import org.sonar.server.issue.notification.DistributedMetricStatsInt;
  53. import org.sonar.server.issue.notification.IssueChangeNotification;
  54. import org.sonar.server.issue.notification.MyNewIssuesNotification;
  55. import org.sonar.server.issue.notification.NewIssuesNotification;
  56. import org.sonar.server.issue.notification.NewIssuesNotificationFactory;
  57. import org.sonar.server.issue.notification.NewIssuesStatistics;
  58. import org.sonar.server.notification.NotificationService;
  59. import static java.util.Arrays.stream;
  60. import static java.util.Collections.shuffle;
  61. import static java.util.stream.Collectors.toList;
  62. import static java.util.stream.Stream.concat;
  63. import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
  64. import static org.apache.commons.lang.math.RandomUtils.nextInt;
  65. import static org.assertj.core.api.Assertions.assertThat;
  66. import static org.mockito.ArgumentCaptor.forClass;
  67. import static org.mockito.ArgumentMatchers.eq;
  68. import static org.mockito.Mockito.any;
  69. import static org.mockito.Mockito.mock;
  70. import static org.mockito.Mockito.never;
  71. import static org.mockito.Mockito.verify;
  72. import static org.mockito.Mockito.when;
  73. import static org.sonar.ce.task.projectanalysis.component.Component.Type;
  74. import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
  75. import static org.sonar.ce.task.projectanalysis.step.SendIssueNotificationsStep.NOTIF_TYPES;
  76. import static org.sonar.db.component.BranchType.PULL_REQUEST;
  77. import static org.sonar.db.component.ComponentTesting.newBranchDto;
  78. import static org.sonar.db.component.ComponentTesting.newFileDto;
  79. import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
  80. import static org.sonar.db.component.ComponentTesting.newProjectBranch;
  81. import static org.sonar.db.issue.IssueTesting.newIssue;
  82. import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
  83. import static org.sonar.db.rule.RuleTesting.newRule;
  84. public class SendIssueNotificationsStepTest extends BaseStepTest {
  85. private static final String BRANCH_NAME = "feature";
  86. private static final String PULL_REQUEST_ID = "pr-123";
  87. private static final long ANALYSE_DATE = 123L;
  88. private static final int FIVE_MINUTES_IN_MS = 1000 * 60 * 5;
  89. private static final Duration ISSUE_DURATION = Duration.create(100L);
  90. private static final Component FILE = builder(Type.FILE, 11).build();
  91. private static final Component PROJECT = builder(Type.PROJECT, 1)
  92. .setProjectVersion(randomAlphanumeric(10))
  93. .addChildren(FILE).build();
  94. @Rule
  95. public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
  96. .setRoot(PROJECT);
  97. @Rule
  98. public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
  99. .setBranch(new DefaultBranchImpl())
  100. .setAnalysisDate(new Date(ANALYSE_DATE));
  101. @Rule
  102. public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();
  103. @Rule
  104. public TemporaryFolder temp = new TemporaryFolder();
  105. @Rule
  106. public DbTester db = DbTester.create(System2.INSTANCE);
  107. private final Random random = new Random();
  108. private final RuleType[] RULE_TYPES_EXCEPT_HOTSPOTS = Stream.of(RuleType.values()).filter(r -> r != RuleType.SECURITY_HOTSPOT).toArray(RuleType[]::new);
  109. private final RuleType randomRuleType = RULE_TYPES_EXCEPT_HOTSPOTS[random.nextInt(RULE_TYPES_EXCEPT_HOTSPOTS.length)];
  110. private NotificationService notificationService = mock(NotificationService.class);
  111. private NewIssuesNotificationFactory newIssuesNotificationFactory = mock(NewIssuesNotificationFactory.class);
  112. private NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
  113. private MyNewIssuesNotification myNewIssuesNotificationMock = createMyNewIssuesNotificationMock();
  114. private IssueCache issueCache;
  115. private SendIssueNotificationsStep underTest;
  116. @Before
  117. public void setUp() throws Exception {
  118. issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
  119. underTest = new SendIssueNotificationsStep(issueCache, ruleRepository, treeRootHolder, notificationService, analysisMetadataHolder,
  120. newIssuesNotificationFactory, db.getDbClient());
  121. when(newIssuesNotificationFactory.newNewIssuesNotification()).thenReturn(newIssuesNotificationMock);
  122. when(newIssuesNotificationFactory.newMyNewIssuesNotification()).thenReturn(myNewIssuesNotificationMock);
  123. }
  124. @Test
  125. public void do_not_send_notifications_if_no_subscribers() {
  126. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(false);
  127. TestComputationStepContext context = new TestComputationStepContext();
  128. underTest.execute(context);
  129. verify(notificationService, never()).deliver(any());
  130. verifyStatistics(context, 0, 0, 0);
  131. }
  132. @Test
  133. public void send_global_new_issues_notification() {
  134. issueCache.newAppender().append(
  135. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
  136. .setCreationDate(new Date(ANALYSE_DATE)))
  137. .close();
  138. when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
  139. TestComputationStepContext context = new TestComputationStepContext();
  140. underTest.execute(context);
  141. verify(notificationService).deliver(newIssuesNotificationMock);
  142. verify(newIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
  143. verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
  144. verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any());
  145. verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
  146. verifyStatistics(context, 1, 0, 0);
  147. }
  148. @Test
  149. public void send_global_new_issues_notification_only_for_non_backdated_issues() {
  150. Random random = new Random();
  151. Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
  152. Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
  153. Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
  154. List<DefaultIssue> issues = concat(stream(efforts)
  155. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  156. .setCreationDate(new Date(ANALYSE_DATE))),
  157. stream(backDatedEfforts)
  158. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  159. .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
  160. .collect(toList());
  161. shuffle(issues);
  162. DiskCache<DefaultIssue>.DiskAppender issueCache = this.issueCache.newAppender();
  163. issues.forEach(issueCache::append);
  164. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  165. TestComputationStepContext context = new TestComputationStepContext();
  166. underTest.execute(context);
  167. verify(notificationService).deliver(newIssuesNotificationMock);
  168. ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
  169. verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
  170. verify(newIssuesNotificationMock).setDebt(expectedEffort);
  171. NewIssuesStatistics.Stats stats = statsCaptor.getValue();
  172. assertThat(stats.hasIssues()).isTrue();
  173. // just checking all issues have been added to the stats
  174. DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
  175. assertThat(severity.getOnLeak()).isEqualTo(efforts.length);
  176. assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
  177. verifyStatistics(context, 1, 0, 0);
  178. }
  179. @Test
  180. public void do_not_send_global_new_issues_notification_if_issue_has_been_backdated() {
  181. issueCache.newAppender().append(
  182. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
  183. .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
  184. .close();
  185. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  186. TestComputationStepContext context = new TestComputationStepContext();
  187. underTest.execute(context);
  188. verify(notificationService, never()).deliver(any());
  189. verifyStatistics(context, 0, 0, 0);
  190. }
  191. @Test
  192. public void send_global_new_issues_notification_on_branch() {
  193. ComponentDto branch = setUpProjectWithBranch();
  194. issueCache.newAppender().append(
  195. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
  196. when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
  197. analysisMetadataHolder.setBranch(newBranch());
  198. TestComputationStepContext context = new TestComputationStepContext();
  199. underTest.execute(context);
  200. verify(notificationService).deliver(newIssuesNotificationMock);
  201. verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME, null);
  202. verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
  203. verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
  204. verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
  205. verifyStatistics(context, 1, 0, 0);
  206. }
  207. @Test
  208. public void send_global_new_issues_notification_on_pull_request() {
  209. ComponentDto branch = setUpProjectWithBranch();
  210. issueCache.newAppender().append(
  211. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
  212. when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
  213. analysisMetadataHolder.setBranch(newPullRequest());
  214. analysisMetadataHolder.setPullRequestKey(PULL_REQUEST_ID);
  215. TestComputationStepContext context = new TestComputationStepContext();
  216. underTest.execute(context);
  217. verify(notificationService).deliver(newIssuesNotificationMock);
  218. verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), null, PULL_REQUEST_ID);
  219. verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
  220. verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
  221. verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
  222. verifyStatistics(context, 1, 0, 0);
  223. }
  224. @Test
  225. public void do_not_send_global_new_issues_notification_on_branch_if_issue_has_been_backdated() {
  226. ComponentDto branch = setUpProjectWithBranch();
  227. issueCache.newAppender().append(
  228. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))).close();
  229. when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
  230. analysisMetadataHolder.setBranch(newBranch());
  231. TestComputationStepContext context = new TestComputationStepContext();
  232. underTest.execute(context);
  233. verify(notificationService, never()).deliver(any());
  234. verifyStatistics(context, 0, 0, 0);
  235. }
  236. @Test
  237. public void send_new_issues_notification_to_user() {
  238. UserDto user = db.users().insertUser();
  239. issueCache.newAppender().append(
  240. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
  241. .setCreationDate(new Date(ANALYSE_DATE)))
  242. .close();
  243. when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
  244. TestComputationStepContext context = new TestComputationStepContext();
  245. underTest.execute(context);
  246. verify(notificationService).deliver(newIssuesNotificationMock);
  247. verify(notificationService).deliver(myNewIssuesNotificationMock);
  248. verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
  249. verify(myNewIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
  250. verify(myNewIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
  251. verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any(NewIssuesStatistics.Stats.class));
  252. verify(myNewIssuesNotificationMock).setDebt(ISSUE_DURATION);
  253. verifyStatistics(context, 1, 1, 0);
  254. }
  255. @Test
  256. public void send_new_issues_notification_to_user_only_for_those_assigned_to_her() throws IOException {
  257. UserDto perceval = db.users().insertUser(u -> u.setLogin("perceval"));
  258. Integer[] assigned = IntStream.range(0, 5).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
  259. Duration expectedEffort = Duration.create(stream(assigned).mapToInt(i -> i).sum());
  260. UserDto arthur = db.users().insertUser(u -> u.setLogin("arthur"));
  261. Integer[] assignedToOther = IntStream.range(0, 3).mapToObj(i -> 10).toArray(Integer[]::new);
  262. List<DefaultIssue> issues = concat(stream(assigned)
  263. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  264. .setAssigneeUuid(perceval.getUuid())
  265. .setCreationDate(new Date(ANALYSE_DATE))),
  266. stream(assignedToOther)
  267. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  268. .setAssigneeUuid(arthur.getUuid())
  269. .setCreationDate(new Date(ANALYSE_DATE))))
  270. .collect(toList());
  271. shuffle(issues);
  272. IssueCache issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
  273. DiskCache<DefaultIssue>.DiskAppender newIssueCache = issueCache.newAppender();
  274. issues.forEach(newIssueCache::append);
  275. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  276. NewIssuesNotificationFactory newIssuesNotificationFactory = mock(NewIssuesNotificationFactory.class);
  277. NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
  278. when(newIssuesNotificationFactory.newNewIssuesNotification()).thenReturn(newIssuesNotificationMock);
  279. MyNewIssuesNotification myNewIssuesNotificationMock1 = createMyNewIssuesNotificationMock();
  280. MyNewIssuesNotification myNewIssuesNotificationMock2 = createMyNewIssuesNotificationMock();
  281. when(newIssuesNotificationFactory.newMyNewIssuesNotification()).thenReturn(myNewIssuesNotificationMock1).thenReturn(myNewIssuesNotificationMock2);
  282. TestComputationStepContext context = new TestComputationStepContext();
  283. new SendIssueNotificationsStep(issueCache, ruleRepository, treeRootHolder, notificationService, analysisMetadataHolder, newIssuesNotificationFactory, db.getDbClient())
  284. .execute(context);
  285. verify(notificationService).deliver(myNewIssuesNotificationMock1);
  286. Map<String, MyNewIssuesNotification> myNewIssuesNotificationMocksByUsersName = new HashMap<>();
  287. ArgumentCaptor<UserDto> userCaptor1 = forClass(UserDto.class);
  288. verify(myNewIssuesNotificationMock1).setAssignee(userCaptor1.capture());
  289. myNewIssuesNotificationMocksByUsersName.put(userCaptor1.getValue().getLogin(), myNewIssuesNotificationMock1);
  290. verify(notificationService).deliver(myNewIssuesNotificationMock2);
  291. ArgumentCaptor<UserDto> userCaptor2 = forClass(UserDto.class);
  292. verify(myNewIssuesNotificationMock2).setAssignee(userCaptor2.capture());
  293. myNewIssuesNotificationMocksByUsersName.put(userCaptor2.getValue().getLogin(), myNewIssuesNotificationMock2);
  294. MyNewIssuesNotification myNewIssuesNotificationMock = myNewIssuesNotificationMocksByUsersName.get("perceval");
  295. ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
  296. verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
  297. verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
  298. NewIssuesStatistics.Stats stats = statsCaptor.getValue();
  299. assertThat(stats.hasIssues()).isTrue();
  300. // just checking all issues have been added to the stats
  301. DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
  302. assertThat(severity.getOnLeak()).isEqualTo(assigned.length);
  303. assertThat(severity.getTotal()).isEqualTo(assigned.length);
  304. verifyStatistics(context, 1, 2, 0);
  305. }
  306. @Test
  307. public void send_new_issues_notification_to_user_only_for_non_backdated_issues() {
  308. UserDto user = db.users().insertUser();
  309. Random random = new Random();
  310. Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
  311. Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
  312. Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
  313. List<DefaultIssue> issues = concat(stream(efforts)
  314. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  315. .setAssigneeUuid(user.getUuid())
  316. .setCreationDate(new Date(ANALYSE_DATE))),
  317. stream(backDatedEfforts)
  318. .map(effort -> new DefaultIssue().setType(randomRuleType).setEffort(Duration.create(effort))
  319. .setAssigneeUuid(user.getUuid())
  320. .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
  321. .collect(toList());
  322. shuffle(issues);
  323. DiskCache<DefaultIssue>.DiskAppender issueCache = this.issueCache.newAppender();
  324. issues.forEach(issueCache::append);
  325. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  326. TestComputationStepContext context = new TestComputationStepContext();
  327. underTest.execute(context);
  328. verify(notificationService).deliver(newIssuesNotificationMock);
  329. verify(notificationService).deliver(myNewIssuesNotificationMock);
  330. verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
  331. ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
  332. verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
  333. verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
  334. NewIssuesStatistics.Stats stats = statsCaptor.getValue();
  335. assertThat(stats.hasIssues()).isTrue();
  336. // just checking all issues have been added to the stats
  337. DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
  338. assertThat(severity.getOnLeak()).isEqualTo(efforts.length);
  339. assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
  340. verifyStatistics(context, 1, 1, 0);
  341. }
  342. @Test
  343. public void do_not_send_new_issues_notification_to_user_if_issue_is_backdated() {
  344. UserDto user = db.users().insertUser();
  345. issueCache.newAppender().append(
  346. new DefaultIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
  347. .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
  348. .close();
  349. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  350. TestComputationStepContext context = new TestComputationStepContext();
  351. underTest.execute(context);
  352. verify(notificationService, never()).deliver(any());
  353. verifyStatistics(context, 0, 0, 0);
  354. }
  355. @Test
  356. public void send_issues_change_notification() {
  357. sendIssueChangeNotification(ANALYSE_DATE);
  358. }
  359. @Test
  360. public void dont_send_issues_change_notification_for_hotspot() {
  361. UserDto user = db.users().insertUser();
  362. ComponentDto project = newPrivateProjectDto(newOrganizationDto()).setDbKey(PROJECT.getDbKey()).setLongName(PROJECT.getName());
  363. ComponentDto file = newFileDto(project).setDbKey(FILE.getDbKey()).setLongName(FILE.getName());
  364. RuleDefinitionDto ruleDefinitionDto = newRule();
  365. DefaultIssue issue = prepareIssue(ANALYSE_DATE, user, project, file, ruleDefinitionDto, RuleType.SECURITY_HOTSPOT);
  366. TestComputationStepContext context = new TestComputationStepContext();
  367. underTest.execute(context);
  368. verify(notificationService, never()).deliver(any());
  369. verifyStatistics(context, 0, 0, 0);
  370. }
  371. @Test
  372. public void send_issues_change_notification_even_if_issue_is_backdated() {
  373. sendIssueChangeNotification(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
  374. }
  375. private void sendIssueChangeNotification(long issueCreatedAt) {
  376. UserDto user = db.users().insertUser();
  377. ComponentDto project = newPrivateProjectDto(newOrganizationDto()).setDbKey(PROJECT.getDbKey()).setLongName(PROJECT.getName());
  378. ComponentDto file = newFileDto(project).setDbKey(FILE.getDbKey()).setLongName(FILE.getName());
  379. RuleDefinitionDto ruleDefinitionDto = newRule();
  380. RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
  381. DefaultIssue issue = prepareIssue(issueCreatedAt, user, project, file, ruleDefinitionDto, randomTypeExceptHotspot);
  382. underTest.execute(new TestComputationStepContext());
  383. ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = forClass(IssueChangeNotification.class);
  384. verify(notificationService).deliver(issueChangeNotificationCaptor.capture());
  385. IssueChangeNotification issueChangeNotification = issueChangeNotificationCaptor.getValue();
  386. assertThat(issueChangeNotification.getFieldValue("key")).isEqualTo(issue.key());
  387. assertThat(issueChangeNotification.getFieldValue("message")).isEqualTo(issue.message());
  388. assertThat(issueChangeNotification.getFieldValue("ruleName")).isEqualTo(ruleDefinitionDto.getName());
  389. assertThat(issueChangeNotification.getFieldValue("projectName")).isEqualTo(project.longName());
  390. assertThat(issueChangeNotification.getFieldValue("projectKey")).isEqualTo(project.getKey());
  391. assertThat(issueChangeNotification.getFieldValue("componentKey")).isEqualTo(file.getKey());
  392. assertThat(issueChangeNotification.getFieldValue("componentName")).isEqualTo(file.longName());
  393. assertThat(issueChangeNotification.getFieldValue("assignee")).isEqualTo(user.getLogin());
  394. }
  395. private DefaultIssue prepareIssue(long issueCreatedAt, UserDto user, ComponentDto project, ComponentDto file, RuleDefinitionDto ruleDefinitionDto, RuleType type) {
  396. DefaultIssue issue = newIssue(ruleDefinitionDto, project, file).setType(type).toDefaultIssue()
  397. .setNew(false).setChanged(true).setSendNotifications(true).setCreationDate(new Date(issueCreatedAt)).setAssigneeUuid(user.getUuid());
  398. ruleRepository.add(ruleDefinitionDto.getKey()).setName(ruleDefinitionDto.getName());
  399. issueCache.newAppender().append(issue).close();
  400. when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
  401. return issue;
  402. }
  403. @Test
  404. public void send_issues_change_notification_on_branch() {
  405. sendIssueChangeNotificationOnBranch(ANALYSE_DATE);
  406. }
  407. @Test
  408. public void send_issues_change_notification_on_branch_even_if_issue_is_backdated() {
  409. sendIssueChangeNotificationOnBranch(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
  410. }
  411. private void sendIssueChangeNotificationOnBranch(long issueCreatedAt) {
  412. ComponentDto project = newPrivateProjectDto(newOrganizationDto());
  413. ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME));
  414. ComponentDto file = newFileDto(branch);
  415. treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getDbKey()).setPublicKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
  416. builder(Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()).build());
  417. RuleDefinitionDto ruleDefinitionDto = newRule();
  418. RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
  419. DefaultIssue issue = newIssue(ruleDefinitionDto, branch, file).setType(randomTypeExceptHotspot).toDefaultIssue()
  420. .setNew(false)
  421. .setChanged(true)
  422. .setSendNotifications(true)
  423. .setCreationDate(new Date(issueCreatedAt));
  424. ruleRepository.add(ruleDefinitionDto.getKey()).setName(ruleDefinitionDto.getName());
  425. issueCache.newAppender().append(issue).close();
  426. when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
  427. analysisMetadataHolder.setBranch(newBranch());
  428. underTest.execute(new TestComputationStepContext());
  429. ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = forClass(IssueChangeNotification.class);
  430. verify(notificationService).deliver(issueChangeNotificationCaptor.capture());
  431. IssueChangeNotification issueChangeNotification = issueChangeNotificationCaptor.getValue();
  432. assertThat(issueChangeNotification.getFieldValue("projectName")).isEqualTo(branch.longName());
  433. assertThat(issueChangeNotification.getFieldValue("projectKey")).isEqualTo(branch.getKey());
  434. assertThat(issueChangeNotification.getFieldValue("branch")).isEqualTo(BRANCH_NAME);
  435. assertThat(issueChangeNotification.getFieldValue("componentKey")).isEqualTo(file.getKey());
  436. assertThat(issueChangeNotification.getFieldValue("componentName")).isEqualTo(file.longName());
  437. }
  438. private NewIssuesNotification createNewIssuesNotificationMock() {
  439. NewIssuesNotification notification = mock(NewIssuesNotification.class);
  440. when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
  441. when(notification.setProjectVersion(any())).thenReturn(notification);
  442. when(notification.setAnalysisDate(any())).thenReturn(notification);
  443. when(notification.setStatistics(any(), any())).thenReturn(notification);
  444. when(notification.setDebt(any())).thenReturn(notification);
  445. return notification;
  446. }
  447. private MyNewIssuesNotification createMyNewIssuesNotificationMock() {
  448. MyNewIssuesNotification notification = mock(MyNewIssuesNotification.class);
  449. when(notification.setAssignee(any(UserDto.class))).thenReturn(notification);
  450. when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
  451. when(notification.setProjectVersion(any())).thenReturn(notification);
  452. when(notification.setAnalysisDate(any())).thenReturn(notification);
  453. when(notification.setStatistics(any(), any())).thenReturn(notification);
  454. when(notification.setDebt(any())).thenReturn(notification);
  455. return notification;
  456. }
  457. private static Branch newBranch() {
  458. Branch branch = mock(Branch.class);
  459. when(branch.isMain()).thenReturn(false);
  460. when(branch.getName()).thenReturn(BRANCH_NAME);
  461. return branch;
  462. }
  463. private static Branch newPullRequest() {
  464. Branch branch = mock(Branch.class);
  465. when(branch.isMain()).thenReturn(false);
  466. when(branch.getType()).thenReturn(PULL_REQUEST);
  467. when(branch.getName()).thenReturn(BRANCH_NAME);
  468. when(branch.getPullRequestKey()).thenReturn(PULL_REQUEST_ID);
  469. return branch;
  470. }
  471. private ComponentDto setUpProjectWithBranch() {
  472. ComponentDto project = newPrivateProjectDto(newOrganizationDto());
  473. ComponentDto branch = newProjectBranch(project, newBranchDto(project).setKey(BRANCH_NAME));
  474. ComponentDto file = newFileDto(branch);
  475. treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getDbKey()).setPublicKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
  476. builder(Type.FILE, 11).setKey(file.getDbKey()).setPublicKey(file.getKey()).setName(file.longName()).build()).build());
  477. return branch;
  478. }
  479. private static void verifyStatistics(TestComputationStepContext context, int expectedNewIssuesNotifications, int expectedMyNewIssuesNotifications, int expectedIssueChangesNotifications) {
  480. context.getStatistics().assertValue("newIssuesNotifs", expectedNewIssuesNotifications);
  481. context.getStatistics().assertValue("myNewIssuesNotifs", expectedMyNewIssuesNotifications);
  482. context.getStatistics().assertValue("changesNotifs", expectedIssueChangesNotifications);
  483. }
  484. @Override
  485. protected ComputationStep step() {
  486. return underTest;
  487. }
  488. }