]> source.dussan.org Git - sonarqube.git/blob
59c2f805cd4be0dedf4e377c15b99183c5d6662f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.telemetry;
21
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.io.StringWriter;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Random;
32 import java.util.Set;
33 import java.util.stream.Collectors;
34 import java.util.stream.IntStream;
35 import org.apache.commons.codec.digest.DigestUtils;
36 import org.jetbrains.annotations.NotNull;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.sonar.api.utils.System2;
40 import org.sonar.api.utils.text.JsonWriter;
41 import org.sonar.core.platform.EditionProvider;
42 import org.sonar.core.telemetry.TelemetryExtension;
43 import org.sonar.core.util.stream.MoreCollectors;
44 import org.sonar.db.user.UserTelemetryDto;
45
46 import static java.lang.String.format;
47 import static java.util.stream.Collectors.joining;
48 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
49 import static org.assertj.core.api.Assertions.assertThat;
50 import static org.mockito.Mockito.mock;
51 import static org.mockito.Mockito.when;
52 import static org.sonar.server.telemetry.TelemetryDataJsonWriter.SCIM_PROPERTY;
53 import static org.sonar.test.JsonAssert.assertJson;
54
55 @RunWith(DataProviderRunner.class)
56 public class TelemetryDataJsonWriterTest {
57
58   private final Random random = new Random();
59
60   private final TelemetryExtension extension = mock(TelemetryExtension.class);
61
62   private final System2 system2 = mock(System2.class);
63
64   private final TelemetryDataJsonWriter underTest = new TelemetryDataJsonWriter(List.of(extension), system2);
65
66   @Test
67   public void write_server_id_version_and_sequence() {
68     TelemetryData data = telemetryBuilder().build();
69
70     String json = writeTelemetryData(data);
71     assertJson(json).isSimilarTo("""
72       {
73         "id": "%s",
74         "version": "%s",
75         "messageSequenceNumber": %s
76       }
77       """.formatted(data.getServerId(), data.getVersion(), data.getMessageSequenceNumber()));
78   }
79
80   @Test
81   public void does_not_write_edition_if_null() {
82     TelemetryData data = telemetryBuilder().build();
83
84     String json = writeTelemetryData(data);
85
86     assertThat(json).doesNotContain("edition");
87   }
88
89   @Test
90   @UseDataProvider("allEditions")
91   public void writes_edition_if_non_null(EditionProvider.Edition edition) {
92     TelemetryData data = telemetryBuilder()
93       .setEdition(edition)
94       .build();
95
96     String json = writeTelemetryData(data);
97     assertJson(json).isSimilarTo("""
98       {
99         "edition": "%s"
100       }
101       """.formatted(edition.name().toLowerCase(Locale.ENGLISH)));
102   }
103
104   @Test
105   public void writes_default_qg() {
106     TelemetryData data = telemetryBuilder()
107       .setDefaultQualityGate("default-qg")
108       .build();
109
110     String json = writeTelemetryData(data);
111     assertJson(json).isSimilarTo("""
112       {
113         "defaultQualityGate": "%s"
114       }
115       """.formatted(data.getDefaultQualityGate()));
116   }
117
118   @Test
119   public void writes_database() {
120     String name = randomAlphabetic(12);
121     String version = randomAlphabetic(10);
122     TelemetryData data = telemetryBuilder()
123       .setDatabase(new TelemetryData.Database(name, version))
124       .build();
125
126     String json = writeTelemetryData(data);
127     assertJson(json).isSimilarTo("""
128       {
129         "database": {
130           "name": "%s",
131           "version": "%s"
132         }
133       }
134       """.formatted(name, version));
135   }
136
137   @Test
138   public void writes_no_plugins() {
139     TelemetryData data = telemetryBuilder()
140       .setPlugins(Collections.emptyMap())
141       .build();
142
143     String json = writeTelemetryData(data);
144
145     assertJson(json).isSimilarTo("""
146       { 
147         "plugins": []
148       }
149       """);
150   }
151
152   @Test
153   public void writes_all_plugins() {
154     Map<String, String> plugins = IntStream.range(0, 1 + random.nextInt(10))
155       .boxed()
156       .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> "V" + i));
157     TelemetryData data = telemetryBuilder()
158       .setPlugins(plugins)
159       .build();
160
161     String json = writeTelemetryData(data);
162     assertJson(json).isSimilarTo("""
163       {
164         "plugins": [%s]
165       }
166       """.formatted(plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(","))));
167   }
168
169   @Test
170   public void does_not_write_installation_date_if_null() {
171     TelemetryData data = telemetryBuilder()
172       .setInstallationDate(null)
173       .build();
174
175     String json = writeTelemetryData(data);
176
177     assertThat(json).doesNotContain("installationDate");
178   }
179
180   @Test
181   public void write_installation_date_in_utc_format() {
182     TelemetryData data = telemetryBuilder()
183       .setInstallationDate(1_000L)
184       .build();
185
186     String json = writeTelemetryData(data);
187
188     assertJson(json).isSimilarTo("""
189       {
190         "installationDate":"1970-01-01T00:00:01+0000"
191       }
192       """);
193   }
194
195   @Test
196   public void does_not_write_installation_version_if_null() {
197     TelemetryData data = telemetryBuilder()
198       .setInstallationVersion(null)
199       .build();
200
201     String json = writeTelemetryData(data);
202
203     assertThat(json).doesNotContain("installationVersion");
204   }
205
206   @Test
207   public void write_installation_version() {
208     String installationVersion = randomAlphabetic(5);
209     TelemetryData data = telemetryBuilder()
210       .setInstallationVersion(installationVersion)
211       .build();
212
213     String json = writeTelemetryData(data);
214     assertJson(json).isSimilarTo("""
215       {
216         "installationVersion": "%s"
217       }
218       """.formatted(installationVersion));
219   }
220
221   @Test
222   @UseDataProvider("getFeatureFlagEnabledStates")
223   public void write_docker_flag(boolean isInDocker) {
224     TelemetryData data = telemetryBuilder()
225       .setInDocker(isInDocker)
226       .build();
227
228     String json = writeTelemetryData(data);
229     assertJson(json).isSimilarTo("""
230       {
231         "docker": %s
232       }
233       """.formatted(isInDocker));
234   }
235
236   @Test
237   @UseDataProvider("getFeatureFlagEnabledStates")
238   public void write_scim_feature_flag(boolean isScimEnabled) {
239     TelemetryData data = telemetryBuilder()
240       .setIsScimEnabled(isScimEnabled)
241       .build();
242
243     String json = writeTelemetryData(data);
244
245     assertJson(json).isSimilarTo("{" + format("  \"%s\":", SCIM_PROPERTY) + isScimEnabled + "}");
246   }
247
248   @Test
249   public void writes_has_unanalyzed_languages() {
250     TelemetryData data = telemetryBuilder()
251       .setHasUnanalyzedC(true)
252       .setHasUnanalyzedCpp(false)
253       .build();
254
255     String json = writeTelemetryData(data);
256
257     assertJson(json).isSimilarTo("""
258       {
259         "hasUnanalyzedC": true,
260         "hasUnanalyzedCpp": false,
261       }
262       """);
263   }
264
265   @Test
266   public void writes_security_custom_config() {
267     TelemetryData data = telemetryBuilder()
268       .setCustomSecurityConfigs(Set.of("php", "java"))
269       .build();
270
271     String json = writeTelemetryData(data);
272
273     assertJson(json).isSimilarTo("""
274       {
275         "customSecurityConfig": ["php", "java"]
276       }
277       """);
278   }
279
280   @Test
281   public void writes_local_timestamp() {
282     when(system2.now()).thenReturn(1000L);
283
284     TelemetryData data = telemetryBuilder().build();
285     String json = writeTelemetryData(data);
286
287     assertJson(json).isSimilarTo("""
288       {
289         "localTimestamp": "1970-01-01T00:00:01+0000"
290       }
291       """);
292   }
293
294   @Test
295   public void writes_all_users_with_anonymous_md5_uuids() {
296     TelemetryData data = telemetryBuilder()
297       .setUsers(attachUsers())
298       .build();
299
300     String json = writeTelemetryData(data);
301
302     assertJson(json).isSimilarTo("""
303       {
304         "users": [
305           {
306             "userUuid": "%s",
307             "status": "active",
308             "identityProvider": "gitlab",
309             "lastActivity": "1970-01-01T00:00:00+0000",
310             "lastSonarlintActivity": "1970-01-01T00:00:00+0000"
311           },
312           {
313             "userUuid": "%s",
314             "status": "inactive",
315             "identityProvider": "gitlab",
316             "lastActivity": "1970-01-01T00:00:00+0000",
317             "lastSonarlintActivity": "1970-01-01T00:00:00+0000"
318           },
319           {
320             "userUuid": "%s",
321             "status": "active",
322             "identityProvider": "gitlab",
323             "lastActivity": "1970-01-01T00:00:00+0000",
324             "lastSonarlintActivity": "1970-01-01T00:00:00+0000"
325           }
326         ]
327       }
328       """
329       .formatted(DigestUtils.sha3_224Hex("uuid-0"), DigestUtils.sha3_224Hex("uuid-1"), DigestUtils.sha3_224Hex("uuid-2")));
330   }
331
332   @Test
333   public void writes_all_projects() {
334     TelemetryData data = telemetryBuilder()
335       .setProjects(attachProjects())
336       .build();
337
338     String json = writeTelemetryData(data);
339
340     assertJson(json).isSimilarTo("""
341       {
342         "projects": [
343           {
344             "projectUuid": "uuid-0",
345             "lastAnalysis": "1970-01-01T00:00:00+0000",
346             "language": "lang-0",
347             "loc": 2
348           },
349           {
350             "projectUuid": "uuid-1",
351             "lastAnalysis": "1970-01-01T00:00:00+0000",
352             "language": "lang-1",
353             "loc": 4
354           },
355           {
356             "projectUuid": "uuid-2",
357             "lastAnalysis": "1970-01-01T00:00:00+0000",
358             "language": "lang-2",
359             "loc": 6
360           }
361         ]
362       }
363       """);
364   }
365
366   @Test
367   public void writeTelemetryData_whenAnalyzedLanguages_shouldwriteAllProjectsStats() {
368     TelemetryData data = telemetryBuilder()
369       .setProjectStatistics(attachProjectStatsWithMetrics())
370       .build();
371
372     String json = writeTelemetryData(data);
373
374     assertJson(json).isSimilarTo("""
375       {
376         "projects-general-stats": [
377           {
378             "projectUuid": "uuid-0",
379             "branchCount": 2,
380             "pullRequestCount": 2,
381             "qualityGate": "qg-0",
382             "scm": "scm-0",
383             "ci": "ci-0",
384             "devopsPlatform": "devops-0",
385             "bugs": 2,
386             "vulnerabilities": 3,
387             "securityHotspots": 4,
388             "technicalDebt": 60,
389             "developmentCost": 30
390           },
391           {
392             "projectUuid": "uuid-1",
393             "branchCount": 4,
394             "pullRequestCount": 4,
395             "qualityGate": "qg-1",
396             "scm": "scm-1",
397             "ci": "ci-1",
398             "devopsPlatform": "devops-1",
399             "bugs": 4,
400             "vulnerabilities": 6,
401             "securityHotspots": 8,
402             "technicalDebt": 120,
403             "developmentCost": 60
404           },
405           {
406             "projectUuid": "uuid-2",
407             "branchCount": 6,
408             "pullRequestCount": 6,
409             "qualityGate": "qg-2",
410             "scm": "scm-2",
411             "ci": "ci-2",
412             "devopsPlatform": "devops-2",
413             "bugs": 6,
414             "vulnerabilities": 9,
415             "securityHotspots": 12,
416             "technicalDebt": 180,
417             "developmentCost": 90
418           }
419         ]
420       }
421       """
422     );
423   }
424
425   @Test
426   public void writes_all_projects_stats_with_unanalyzed_languages() {
427     TelemetryData data = telemetryBuilder()
428       .setProjectStatistics(attachProjectStats())
429       .build();
430
431     String json = writeTelemetryData(data);
432     assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp");
433   }
434
435   @Test
436   public void writes_all_projects_stats_without_missing_metrics() {
437     TelemetryData data = telemetryBuilder()
438       .setProjectStatistics(attachProjectStats())
439       .build();
440     String json = writeTelemetryData(data);
441     assertThat(json).doesNotContain("bugs", "vulnerabilities", "securityHotspots", "technicalDebt", "developmentCost");
442   }
443
444   @Test
445   public void writes_all_quality_gates() {
446     TelemetryData data = telemetryBuilder()
447       .setQualityGates(attachQualityGates())
448       .build();
449
450     String json = writeTelemetryData(data);
451     assertJson(json).isSimilarTo("""
452       {
453         "quality-gates": [
454           {
455             "uuid": "uuid-0",
456             "caycStatus": "non-compliant"
457           },
458           {
459             "uuid": "uuid-1",
460             "caycStatus": "compliant"
461           },
462           {
463             "uuid": "uuid-2",
464             "caycStatus": "over-compliant"
465           }
466         ]
467       }
468       """
469     );
470   }
471
472   private static TelemetryData.Builder telemetryBuilder() {
473     return TelemetryData.builder()
474       .setServerId("foo")
475       .setVersion("bar")
476       .setMessageSequenceNumber(1L)
477       .setPlugins(Collections.emptyMap())
478       .setDatabase(new TelemetryData.Database("H2", "11"));
479   }
480
481   @NotNull
482   private static List<UserTelemetryDto> attachUsers() {
483     return IntStream.range(0, 3)
484       .mapToObj(
485         i -> new UserTelemetryDto().setUuid("uuid-" + i).setActive(i % 2 == 0).setLastConnectionDate(1L).setLastSonarlintConnectionDate(2L).setExternalIdentityProvider("gitlab"))
486       .collect(Collectors.toList());
487   }
488
489   private static List<TelemetryData.Project> attachProjects() {
490     return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).collect(Collectors.toList());
491   }
492
493   private static List<TelemetryData.ProjectStatistics> attachProjectStatsWithMetrics() {
494     return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsWithMetricBuilder(i).build()).toList();
495   }
496
497   private static List<TelemetryData.ProjectStatistics> attachProjectStats() {
498     return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsBuilder(i).build()).toList();
499   }
500
501   private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsBuilder(int i) {
502     return new TelemetryData.ProjectStatistics.Builder()
503       .setProjectUuid("uuid-" + i)
504       .setBranchCount((i + 1L) * 2L)
505       .setPRCount((i + 1L) * 2L)
506       .setQG("qg-" + i).setCi("ci-" + i)
507       .setScm("scm-" + i)
508       .setDevops("devops-" + i);
509   }
510
511   private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsWithMetricBuilder(int i) {
512     return getProjectStatisticsBuilder(i)
513       .setBugs((i + 1L) * 2)
514       .setVulnerabilities((i + 1L) * 3)
515       .setSecurityHotspots((i + 1L) * 4)
516       .setDevelopmentCost((i + 1L) * 30d)
517       .setTechnicalDebt((i + 1L) * 60d);
518   }
519
520   private List<TelemetryData.QualityGate> attachQualityGates() {
521     return List.of(new TelemetryData.QualityGate("uuid-0", "non-compliant"),
522       new TelemetryData.QualityGate("uuid-1", "compliant"),
523       new TelemetryData.QualityGate("uuid-2", "over-compliant"));
524   }
525
526   @DataProvider
527   public static Object[][] allEditions() {
528     return Arrays.stream(EditionProvider.Edition.values())
529       .map(t -> new Object[]{t})
530       .toArray(Object[][]::new);
531   }
532
533   private String writeTelemetryData(TelemetryData data) {
534     StringWriter jsonString = new StringWriter();
535     try (JsonWriter json = JsonWriter.of(jsonString)) {
536       underTest.writeTelemetryData(json, data);
537     }
538     return jsonString.toString();
539   }
540
541   @DataProvider
542   public static Set<Boolean> getFeatureFlagEnabledStates() {
543     return Set.of(true, false);
544   }
545 }