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.ParamChange;
32 import org.sonar.core.util.RuleChange;
33 import org.sonar.core.util.RuleSetChangedEvent;
34 import org.sonar.server.exceptions.ForbiddenException;
35 import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor;
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.mockito.ArgumentMatchers.any;
39 import static org.mockito.ArgumentMatchers.anySet;
40 import static org.mockito.ArgumentMatchers.anyString;
41 import static org.mockito.Mockito.clearInvocations;
42 import static org.mockito.Mockito.doThrow;
43 import static org.mockito.Mockito.mock;
44 import static org.mockito.Mockito.times;
45 import static org.mockito.Mockito.verify;
46 import static org.mockito.Mockito.verifyNoInteractions;
47 import static org.mockito.Mockito.when;
48 import static org.sonar.test.EventAssert.assertThatEvent;
50 public class SonarLintClientsRegistryTest {
52 private final AsyncContext defaultAsyncContext = mock(AsyncContext.class);
54 private final Set<String> exampleKeys = Set.of("project1", "project2", "project3");
55 private final Set<String> languageKeys = Set.of("language1", "language2", "language3");
56 private final String USER_UUID = "userUuid";
57 private final ServletResponse response = mock(ServletResponse.class);
58 private final ServletOutputStream outputStream = mock(ServletOutputStream.class);
60 private final SonarLintClientPermissionsValidator permissionsValidator = mock(SonarLintClientPermissionsValidator.class);
61 private final StandaloneRuleActivatorEventsDistributor eventsDistributor = mock(StandaloneRuleActivatorEventsDistributor.class);
63 private SonarLintClientsRegistry underTest;
66 public void before() {
67 underTest = new SonarLintClientsRegistry(eventsDistributor, permissionsValidator);
71 public void registerClientAndUnregister_changesNumberOfClients_andClosesClient() {
72 SonarLintClient sonarLintClient = mock(SonarLintClient.class);
74 underTest.registerClient(sonarLintClient);
76 assertThat(underTest.countConnectedClients()).isEqualTo(1);
78 underTest.unregisterClient(sonarLintClient);
80 assertThat(underTest.countConnectedClients()).isZero();
81 verify(sonarLintClient).close();
85 public void registering10Clients_10ClientsAreRegistered() {
86 for (int i = 0; i < 10; i++) {
87 AsyncContext newAsyncContext = mock(AsyncContext.class);
88 SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleKeys, languageKeys, USER_UUID);
89 underTest.registerClient(sonarLintClient);
92 assertThat(underTest.countConnectedClients()).isEqualTo(10);
96 public void listen_givenOneClientInterestedInJavaEvents_sendOneJavaEvent() throws IOException {
97 Set<String> javaLanguageKey = Set.of("java");
98 when(defaultAsyncContext.getResponse()).thenReturn(response);
99 when(response.getOutputStream()).thenReturn(outputStream);
100 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey, USER_UUID);
102 underTest.registerClient(sonarLintClient);
104 RuleChange javaRule = createRuleChange();
106 RuleChange[] activatedRules = {javaRule};
107 String[] deactivatedRules = {"repo2:rule-key2"};
108 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
109 underTest.listen(ruleSetChangedEvent);
111 ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
112 verify(outputStream).write(captor.capture());
113 String message = new String(captor.getValue());
114 assertThatEvent(message)
115 .hasType("RuleSetChanged")
116 .hasJsonData(getClass().getResource("rule-change-event-data.json"));
120 public void listen_givenOneClientInterestedInJsEventsAndJavaEventGenerated_sendZeroEvents() throws IOException {
121 Set<String> jsLanguageKey = Set.of("js");
122 when(defaultAsyncContext.getResponse()).thenReturn(response);
123 when(response.getOutputStream()).thenReturn(outputStream);
124 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey, USER_UUID);
126 underTest.registerClient(sonarLintClient);
128 RuleChange[] activatedRules = {};
129 String[] deactivatedRules = {"repo:rule-key"};
130 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
131 underTest.listen(ruleSetChangedEvent);
133 verifyNoInteractions(outputStream);
137 public void listen_givenOneClientInterestedInProjA_DontCheckPermissionsForProjB() throws IOException {
138 when(defaultAsyncContext.getResponse()).thenReturn(response);
139 when(response.getOutputStream()).thenReturn(outputStream);
140 Set<String> clientProjectKeys = Set.of("projA");
141 Set<String> eventProjectKeys = Set.of("projA", "projB");
142 SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, clientProjectKeys, Set.of("java"), USER_UUID);
144 underTest.registerClient(sonarLintClient);
146 RuleChange[] activatedRules = {};
147 String[] deactivatedRules = {"repo:rule-key"};
148 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(eventProjectKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
149 underTest.listen(ruleSetChangedEvent);
151 ArgumentCaptor<Set<String>> argument = ArgumentCaptor.forClass(Set.class);
152 verify(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), argument.capture());
153 assertThat(argument.getValue()).isEqualTo(clientProjectKeys);
157 public void listen_givenUserNotPermittedToReceiveEvent_closeConnection() {
158 RuleChange[] activatedRules = {};
159 String[] deactivatedRules = {"repo:rule-key"};
160 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
162 SonarLintClient sonarLintClient = createSampleSLClient();
163 underTest.registerClient(sonarLintClient);
164 doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
166 underTest.listen(ruleSetChangedEvent);
168 verify(sonarLintClient).close();
172 public void listen_givenUnregisteredClient_closeConnection() throws IOException {
173 RuleChange[] activatedRules = {};
174 String[] deactivatedRules = {"repo:rule-key"};
175 RuleSetChangedEvent ruleSetChangedEvent = new RuleSetChangedEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules, "java");
177 SonarLintClient sonarLintClient = createSampleSLClient();
178 underTest.registerClient(sonarLintClient);
179 doThrow(new IOException("Broken pipe")).when(sonarLintClient).writeAndFlush(anyString());
181 underTest.listen(ruleSetChangedEvent);
183 underTest.registerClient(sonarLintClient);
184 doThrow(new IllegalStateException("Things went wrong")).when(sonarLintClient).writeAndFlush(anyString());
186 underTest.listen(ruleSetChangedEvent);
188 verify(sonarLintClient, times(2)).close();
192 public void registerClient_whenCalledFirstTime_registerAlsoToListenToEvents() {
193 underTest.registerClient(createSampleSLClient());
195 verify(eventsDistributor).subscribe(underTest);
199 public void registerClient_whenCalledSecondTime_doNotRegisterToEvents() {
200 underTest.registerClient(createSampleSLClient());
201 clearInvocations(eventsDistributor);
203 underTest.registerClient(createSampleSLClient());
204 verifyNoInteractions(eventsDistributor);
208 public void registerClient_whenExceptionAndCalledSecondTime_registerToEvents() {
209 doThrow(new RuntimeException()).when(eventsDistributor).subscribe(any());
210 underTest.registerClient(createSampleSLClient());
211 clearInvocations(eventsDistributor);
213 underTest.registerClient(createSampleSLClient());
214 verify(eventsDistributor).subscribe(underTest);
217 private SonarLintClient createSampleSLClient() {
218 SonarLintClient mock = mock(SonarLintClient.class);
219 when(mock.getLanguages()).thenReturn(Set.of("java"));
220 when(mock.getClientProjectKeys()).thenReturn(exampleKeys);
221 when(mock.getUserUuid()).thenReturn("userUuid");
225 private RuleChange createRuleChange() {
226 RuleChange javaRule = new RuleChange();
227 javaRule.setLanguage("java");
228 javaRule.setParams(new ParamChange[]{new ParamChange("param-key", "param-value")});
229 javaRule.setTemplateKey("repo:template-key");
230 javaRule.setSeverity(Severity.CRITICAL);
231 javaRule.setKey("repo:rule-key");