3 * Copyright (C) 2009-2022 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.pushapi.sonarlint;
22 import java.io.IOException;
24 import javax.servlet.AsyncContext;
25 import javax.servlet.ServletOutputStream;
26 import javax.servlet.ServletResponse;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.mockito.ArgumentCaptor;
30 import org.sonar.api.rule.Severity;
31 import org.sonar.core.util.issue.Issue;
32 import org.sonar.core.util.issue.IssueChangedEvent;
33 import org.sonar.core.util.ParamChange;
34 import org.sonar.core.util.rule.RuleChange;
35 import org.sonar.core.util.rule.RuleSetChangedEvent;
36 import org.sonar.server.exceptions.ForbiddenException;
37 import org.sonar.server.pushapi.issues.StandaloneIssueChangeEventsDistributor;
38 import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.mockito.ArgumentMatchers.any;
42 import static org.mockito.ArgumentMatchers.anySet;
43 import static org.mockito.ArgumentMatchers.anyString;
44 import static org.mockito.Mockito.clearInvocations;
45 import static org.mockito.Mockito.doThrow;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.times;
48 import static org.mockito.Mockito.verify;
49 import static org.mockito.Mockito.verifyNoInteractions;
50 import static org.mockito.Mockito.when;
51 import static org.sonar.test.EventAssert.assertThatEvent;
53 public class SonarLintClientsRegistryTest {
55 private final AsyncContext defaultAsyncContext = mock(AsyncContext.class);
57 private final Set<String> exampleKeys = Set.of("project1", "project2", "project3");
58 private final Set<String> languageKeys = Set.of("language1", "language2", "language3");
59 private final String USER_UUID = "userUuid";
60 private final ServletResponse response = mock(ServletResponse.class);
61 private final ServletOutputStream outputStream = mock(ServletOutputStream.class);
63 private final SonarLintClientPermissionsValidator permissionsValidator = mock(SonarLintClientPermissionsValidator.class);
64 private final StandaloneRuleActivatorEventsDistributor ruleEventsDistributor = mock(StandaloneRuleActivatorEventsDistributor.class);
65 private final StandaloneIssueChangeEventsDistributor issueChangeEventsDistributor = mock(StandaloneIssueChangeEventsDistributor.class);
67 private SonarLintClientsRegistry underTest;
70 public void before() {
71 underTest = new SonarLintClientsRegistry(issueChangeEventsDistributor, ruleEventsDistributor, permissionsValidator);
75 public void registerClientAndUnregister_changesNumberOfClients_andClosesClient() {
76 SonarLintClient sonarLintClient = mock(SonarLintClient.class);
78 underTest.registerClient(sonarLintClient);
80 assertThat(underTest.countConnectedClients()).isEqualTo(1);
82 underTest.unregisterClient(sonarLintClient);
84 assertThat(underTest.countConnectedClients()).isZero();
85 verify(sonarLintClient).close();
89 public void registering10Clients_10ClientsAreRegistered() {
90 for (int i = 0; i < 10; i++) {
91 AsyncContext newAsyncContext = mock(AsyncContext.class);
92 SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleKeys, languageKeys, USER_UUID);
93 underTest.registerClient(sonarLintClient);
96 assertThat(underTest.countConnectedClients()).isEqualTo(10);
100 public void listen_givenOneClientInterestedInJavaEvents_sendOneJavaEvent() throws IOException {
101 Set<String> javaLanguageKey = Set.of("java");
102 when(defaultAsyncContext.getResponse()).thenReturn(response);
103 when(response.getOutputStream()).thenReturn(outputStream);
104 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey, USER_UUID);
106 underTest.registerClient(sonarLintClient);
108 RuleChange javaRule = createRuleChange();
110 RuleChange[] activatedRules = {javaRule};
111 String[] deactivatedRules = {"repo2:rule-key2"};
112 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
113 underTest.listen(ruleSetChangedEvent);
115 ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
116 verify(outputStream).write(captor.capture());
117 String message = new String(captor.getValue());
118 assertThatEvent(message)
119 .hasType("RuleSetChanged")
120 .hasJsonData(getClass().getResource("rule-change-event-data.json"));
124 public void listen_givenOneClientInterestedInJsEventsAndJavaEventGenerated_sendZeroEvents() throws IOException {
125 Set<String> jsLanguageKey = Set.of("js");
126 when(defaultAsyncContext.getResponse()).thenReturn(response);
127 when(response.getOutputStream()).thenReturn(outputStream);
128 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey, USER_UUID);
130 underTest.registerClient(sonarLintClient);
132 RuleChange[] activatedRules = {};
133 String[] deactivatedRules = {"repo:rule-key"};
134 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
135 underTest.listen(ruleSetChangedEvent);
137 verifyNoInteractions(outputStream);
141 public void listen_givenOneClientInterestedInProjA_DontCheckPermissionsForProjB() throws IOException {
142 when(defaultAsyncContext.getResponse()).thenReturn(response);
143 when(response.getOutputStream()).thenReturn(outputStream);
144 Set<String> clientProjectKeys = Set.of("projA");
145 Set<String> eventProjectKeys = Set.of("projA", "projB");
146 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, clientProjectKeys, Set.of("java"), USER_UUID);
148 underTest.registerClient(sonarLintClient);
150 RuleChange[] activatedRules = {};
151 String[] deactivatedRules = {"repo:rule-key"};
152 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(eventProjectKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
153 underTest.listen(ruleSetChangedEvent);
155 ArgumentCaptor<Set<String>> argument = ArgumentCaptor.forClass(Set.class);
156 verify(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), argument.capture());
157 assertThat(argument.getValue()).isEqualTo(clientProjectKeys);
161 public void listen_givenUserNotPermittedToReceiveRuleSetChangedEvent_closeConnection() {
162 RuleChange[] activatedRules = {};
163 String[] deactivatedRules = {"repo:rule-key"};
164 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
166 SonarLintClient sonarLintClient = createSampleSLClient();
167 underTest.registerClient(sonarLintClient);
168 doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
170 underTest.listen(ruleSetChangedEvent);
172 verify(sonarLintClient).close();
176 public void listen_givenUserNotPermittedToReceiveIssueChangeEvent_closeConnection() {
177 Issue[] issues = new Issue[]{ new Issue("issue-1", "branch-1")};
178 IssueChangedEvent issueChangedEvent = new IssueChangedEvent("project1", issues, true, "BLOCKER", "BUG");
180 SonarLintClient sonarLintClient = createSampleSLClient();
181 underTest.registerClient(sonarLintClient);
182 doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
184 underTest.listen(issueChangedEvent);
186 verify(sonarLintClient).close();
190 public void listen_givenUnregisteredClient_closeConnection() throws IOException {
191 RuleChange[] activatedRules = {};
192 String[] deactivatedRules = {"repo:rule-key"};
193 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
195 SonarLintClient sonarLintClient = createSampleSLClient();
196 underTest.registerClient(sonarLintClient);
197 doThrow(new IOException("Broken pipe")).when(sonarLintClient).writeAndFlush(anyString());
199 underTest.listen(ruleSetChangedEvent);
201 underTest.registerClient(sonarLintClient);
202 doThrow(new IllegalStateException("Things went wrong")).when(sonarLintClient).writeAndFlush(anyString());
204 underTest.listen(ruleSetChangedEvent);
206 verify(sonarLintClient, times(2)).close();
210 public void registerClient_whenCalledFirstTime_registerAlsoToListenToEvents() {
211 underTest.registerClient(createSampleSLClient());
213 verify(ruleEventsDistributor).subscribe(underTest);
214 verify(issueChangeEventsDistributor).subscribe(underTest);
218 public void registerClient_whenCalledSecondTime_doNotRegisterToEvents() {
219 underTest.registerClient(createSampleSLClient());
220 clearInvocations(ruleEventsDistributor);
221 clearInvocations(issueChangeEventsDistributor);
223 underTest.registerClient(createSampleSLClient());
224 verifyNoInteractions(ruleEventsDistributor);
225 verifyNoInteractions(issueChangeEventsDistributor);
229 public void registerClient_whenExceptionAndCalledSecondTime_registerToRuleEvents() {
230 doThrow(new RuntimeException()).when(ruleEventsDistributor).subscribe(any());
231 underTest.registerClient(createSampleSLClient());
232 clearInvocations(ruleEventsDistributor);
234 underTest.registerClient(createSampleSLClient());
235 verify(ruleEventsDistributor).subscribe(underTest);
239 public void registerClient_whenExceptionAndCalledSecondTime_registerToIssueChangeEvents() {
240 doThrow(new RuntimeException()).when(issueChangeEventsDistributor).subscribe(any());
241 underTest.registerClient(createSampleSLClient());
242 clearInvocations(issueChangeEventsDistributor);
244 underTest.registerClient(createSampleSLClient());
245 verify(issueChangeEventsDistributor).subscribe(underTest);
248 private SonarLintClient createSampleSLClient() {
249 SonarLintClient mock = mock(SonarLintClient.class);
250 when(mock.getLanguages()).thenReturn(Set.of("java"));
251 when(mock.getClientProjectKeys()).thenReturn(exampleKeys);
252 when(mock.getUserUuid()).thenReturn("userUuid");
256 private RuleChange createRuleChange() {
257 RuleChange javaRule = new RuleChange();
258 javaRule.setLanguage("java");
259 javaRule.setParams(new ParamChange[]{new ParamChange("param-key", "param-value")});
260 javaRule.setTemplateKey("repo:template-key");
261 javaRule.setSeverity(Severity.CRITICAL);
262 javaRule.setKey("repo:rule-key");