]> source.dussan.org Git - sonarqube.git/blob
30144e50ac42c05cc6d3eec252b5cf7c4bbafba8
[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.util.stream.Collectors.joining;
47 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
48 import static org.assertj.core.api.Assertions.assertThat;
49 import static org.mockito.Mockito.mock;
50 import static org.mockito.Mockito.when;
51 import static org.sonar.db.newcodeperiod.NewCodePeriodType.NUMBER_OF_DAYS;
52 import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
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   private static final int NCD_ID = 12345;
67
68   private static final TelemetryData.NewCodeDefinition NCD_INSTANCE =
69     new TelemetryData.NewCodeDefinition(PREVIOUS_VERSION.name(), "", "instance");
70   private static final TelemetryData.NewCodeDefinition NCD_PROJECT =
71     new TelemetryData.NewCodeDefinition(NUMBER_OF_DAYS.name(), "30", "project");
72
73   @Test
74   public void write_server_id_version_and_sequence() {
75     TelemetryData data = telemetryBuilder().build();
76
77     String json = writeTelemetryData(data);
78     assertJson(json).isSimilarTo("""
79       {
80         "id": "%s",
81         "version": "%s",
82         "messageSequenceNumber": %s
83       }
84       """.formatted(data.getServerId(), data.getVersion(), data.getMessageSequenceNumber()));
85   }
86
87   @Test
88   public void does_not_write_edition_if_null() {
89     TelemetryData data = telemetryBuilder().build();
90
91     String json = writeTelemetryData(data);
92
93     assertThat(json).doesNotContain("edition");
94   }
95
96   @Test
97   @UseDataProvider("allEditions")
98   public void writes_edition_if_non_null(EditionProvider.Edition edition) {
99     TelemetryData data = telemetryBuilder()
100       .setEdition(edition)
101       .build();
102
103     String json = writeTelemetryData(data);
104     assertJson(json).isSimilarTo("""
105       {
106         "edition": "%s"
107       }
108       """.formatted(edition.name().toLowerCase(Locale.ENGLISH)));
109   }
110
111   @Test
112   public void writes_default_qg() {
113     TelemetryData data = telemetryBuilder()
114       .setDefaultQualityGate("default-qg")
115       .build();
116
117     String json = writeTelemetryData(data);
118     assertJson(json).isSimilarTo("""
119       {
120         "defaultQualityGate": "%s"
121       }
122       """.formatted(data.getDefaultQualityGate()));
123   }
124
125   @Test
126   public void writes_database() {
127     String name = randomAlphabetic(12);
128     String version = randomAlphabetic(10);
129     TelemetryData data = telemetryBuilder()
130       .setDatabase(new TelemetryData.Database(name, version))
131       .build();
132
133     String json = writeTelemetryData(data);
134     assertJson(json).isSimilarTo("""
135       {
136         "database": {
137           "name": "%s",
138           "version": "%s"
139         }
140       }
141       """.formatted(name, version));
142   }
143
144   @Test
145   public void writes_no_plugins() {
146     TelemetryData data = telemetryBuilder()
147       .setPlugins(Collections.emptyMap())
148       .build();
149
150     String json = writeTelemetryData(data);
151
152     assertJson(json).isSimilarTo("""
153       { 
154         "plugins": []
155       }
156       """);
157   }
158
159   @Test
160   public void writes_all_plugins() {
161     Map<String, String> plugins = IntStream.range(0, 1 + random.nextInt(10))
162       .boxed()
163       .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> "V" + i));
164     TelemetryData data = telemetryBuilder()
165       .setPlugins(plugins)
166       .build();
167
168     String json = writeTelemetryData(data);
169     assertJson(json).isSimilarTo("""
170       {
171         "plugins": [%s]
172       }
173       """.formatted(plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(","))));
174   }
175
176   @Test
177   public void does_not_write_installation_date_if_null() {
178     TelemetryData data = telemetryBuilder()
179       .setInstallationDate(null)
180       .build();
181
182     String json = writeTelemetryData(data);
183
184     assertThat(json).doesNotContain("installationDate");
185   }
186
187   @Test
188   public void write_installation_date_in_utc_format() {
189     TelemetryData data = telemetryBuilder()
190       .setInstallationDate(1_000L)
191       .build();
192
193     String json = writeTelemetryData(data);
194
195     assertJson(json).isSimilarTo("""
196       {
197         "installationDate":"1970-01-01T00:00:01+0000"
198       }
199       """);
200   }
201
202   @Test
203   public void does_not_write_installation_version_if_null() {
204     TelemetryData data = telemetryBuilder()
205       .setInstallationVersion(null)
206       .build();
207
208     String json = writeTelemetryData(data);
209
210     assertThat(json).doesNotContain("installationVersion");
211   }
212
213   @Test
214   public void write_installation_version() {
215     String installationVersion = randomAlphabetic(5);
216     TelemetryData data = telemetryBuilder()
217       .setInstallationVersion(installationVersion)
218       .build();
219
220     String json = writeTelemetryData(data);
221     assertJson(json).isSimilarTo("""
222       {
223         "installationVersion": "%s"
224       }
225       """.formatted(installationVersion));
226   }
227
228   @Test
229   @UseDataProvider("getFeatureFlagEnabledStates")
230   public void write_docker_flag(boolean isInDocker) {
231     TelemetryData data = telemetryBuilder()
232       .setInDocker(isInDocker)
233       .build();
234
235     String json = writeTelemetryData(data);
236     assertJson(json).isSimilarTo("""
237       {
238         "docker": %s
239       }
240       """.formatted(isInDocker));
241   }
242
243   @DataProvider
244   public static Object[][] getManagedInstanceData() {
245     return new Object[][] {
246       {true, "scim"},
247       {true, "github"},
248       {true, "gitlab"},
249       {false, null},
250     };
251   }
252
253   @Test
254   @UseDataProvider("getManagedInstanceData")
255   public void writeTelemetryData_encodesCorrectlyManagedInstanceInformation(boolean isManaged, String provider) {
256     TelemetryData data = telemetryBuilder()
257       .setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(isManaged, provider))
258       .build();
259
260     String json = writeTelemetryData(data);
261
262     if (isManaged) {
263       assertJson(json).isSimilarTo("""
264         {
265         "managedInstanceInformation": {
266           "isManaged": true,
267             "provider": "%s"
268           }
269         }
270         """.formatted(provider));
271     } else {
272       assertJson(json).isSimilarTo("""
273         {
274         "managedInstanceInformation": {
275           "isManaged": false
276           }
277         }
278         """);
279     }
280   }
281
282   @Test
283   public void writes_has_unanalyzed_languages() {
284     TelemetryData data = telemetryBuilder()
285       .setHasUnanalyzedC(true)
286       .setHasUnanalyzedCpp(false)
287       .build();
288
289     String json = writeTelemetryData(data);
290
291     assertJson(json).isSimilarTo("""
292       {
293         "hasUnanalyzedC": true,
294         "hasUnanalyzedCpp": false,
295       }
296       """);
297   }
298
299   @Test
300   public void writes_security_custom_config() {
301     TelemetryData data = telemetryBuilder()
302       .setCustomSecurityConfigs(Set.of("php", "java"))
303       .build();
304
305     String json = writeTelemetryData(data);
306
307     assertJson(json).isSimilarTo("""
308       {
309         "customSecurityConfig": ["php", "java"]
310       }
311       """);
312   }
313
314   @Test
315   public void writes_local_timestamp() {
316     when(system2.now()).thenReturn(1000L);
317
318     TelemetryData data = telemetryBuilder().build();
319     String json = writeTelemetryData(data);
320
321     assertJson(json).isSimilarTo("""
322       {
323         "localTimestamp": "1970-01-01T00:00:01+0000"
324       }
325       """);
326   }
327
328   @Test
329   public void writes_all_users_with_anonymous_md5_uuids() {
330     TelemetryData data = telemetryBuilder()
331       .setUsers(attachUsers())
332       .build();
333
334     String json = writeTelemetryData(data);
335
336     assertJson(json).isSimilarTo("""
337       {
338         "users": [
339           {
340             "userUuid": "%s",
341             "status": "active",
342             "identityProvider": "gitlab",
343             "lastActivity": "1970-01-01T00:00:00+0000",
344             "lastSonarlintActivity": "1970-01-01T00:00:00+0000",
345             "managed": true
346           },
347           {
348             "userUuid": "%s",
349             "status": "inactive",
350             "identityProvider": "gitlab",
351             "lastActivity": "1970-01-01T00:00:00+0000",
352             "lastSonarlintActivity": "1970-01-01T00:00:00+0000",
353             "managed": false
354           },
355           {
356             "userUuid": "%s",
357             "status": "active",
358             "identityProvider": "gitlab",
359             "lastActivity": "1970-01-01T00:00:00+0000",
360             "lastSonarlintActivity": "1970-01-01T00:00:00+0000",
361             "managed": true
362           }
363         ]
364       }
365       """
366       .formatted(DigestUtils.sha3_224Hex("uuid-0"), DigestUtils.sha3_224Hex("uuid-1"), DigestUtils.sha3_224Hex("uuid-2")));
367   }
368
369   @Test
370   public void writes_all_projects() {
371     TelemetryData data = telemetryBuilder()
372       .setProjects(attachProjects())
373       .build();
374
375     String json = writeTelemetryData(data);
376
377     assertJson(json).isSimilarTo("""
378       {
379         "projects": [
380           {
381             "projectUuid": "uuid-0",
382             "lastAnalysis": "1970-01-01T00:00:00+0000",
383             "language": "lang-0",
384             "loc": 2
385           },
386           {
387             "projectUuid": "uuid-1",
388             "lastAnalysis": "1970-01-01T00:00:00+0000",
389             "language": "lang-1",
390             "loc": 4
391           },
392           {
393             "projectUuid": "uuid-2",
394             "lastAnalysis": "1970-01-01T00:00:00+0000",
395             "language": "lang-2",
396             "loc": 6
397           }
398         ]
399       }
400       """);
401   }
402
403   @Test
404   public void writeTelemetryData_whenAnalyzedLanguages_shouldwriteAllProjectsStats() {
405     TelemetryData data = telemetryBuilder()
406       .setProjectStatistics(attachProjectStatsWithMetrics())
407       .build();
408
409     String json = writeTelemetryData(data);
410
411     assertJson(json).isSimilarTo("""
412       {
413         "projects-general-stats": [
414           {
415             "projectUuid": "uuid-0",
416             "branchCount": 2,
417             "pullRequestCount": 2,
418             "qualityGate": "qg-0",
419             "scm": "scm-0",
420             "ci": "ci-0",
421             "devopsPlatform": "devops-0",
422             "bugs": 2,
423             "vulnerabilities": 3,
424             "securityHotspots": 4,
425             "technicalDebt": 60,
426             "developmentCost": 30,
427             "ncdId": 12345
428           },
429           {
430             "projectUuid": "uuid-1",
431             "branchCount": 4,
432             "pullRequestCount": 4,
433             "qualityGate": "qg-1",
434             "scm": "scm-1",
435             "ci": "ci-1",
436             "devopsPlatform": "devops-1",
437             "bugs": 4,
438             "vulnerabilities": 6,
439             "securityHotspots": 8,
440             "technicalDebt": 120,
441             "developmentCost": 60,
442             "ncdId": 12345
443           },
444           {
445             "projectUuid": "uuid-2",
446             "branchCount": 6,
447             "pullRequestCount": 6,
448             "qualityGate": "qg-2",
449             "scm": "scm-2",
450             "ci": "ci-2",
451             "devopsPlatform": "devops-2",
452             "bugs": 6,
453             "vulnerabilities": 9,
454             "securityHotspots": 12,
455             "technicalDebt": 180,
456             "developmentCost": 90,
457             "ncdId": 12345
458           }
459         ]
460       }
461       """
462     );
463   }
464
465   @Test
466   public void writes_all_projects_stats_with_unanalyzed_languages() {
467     TelemetryData data = telemetryBuilder()
468       .setProjectStatistics(attachProjectStats())
469       .build();
470
471     String json = writeTelemetryData(data);
472     assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp");
473   }
474
475   @Test
476   public void writes_all_projects_stats_without_missing_metrics() {
477     TelemetryData data = telemetryBuilder()
478       .setProjectStatistics(attachProjectStats())
479       .build();
480     String json = writeTelemetryData(data);
481     assertThat(json).doesNotContain("bugs", "vulnerabilities", "securityHotspots", "technicalDebt", "developmentCost");
482   }
483
484   @Test
485   public void writes_all_quality_gates() {
486     TelemetryData data = telemetryBuilder()
487       .setQualityGates(attachQualityGates())
488       .build();
489
490     String json = writeTelemetryData(data);
491     assertJson(json).isSimilarTo("""
492       {
493         "quality-gates": [
494           {
495             "uuid": "uuid-0",
496             "caycStatus": "non-compliant"
497           },
498           {
499             "uuid": "uuid-1",
500             "caycStatus": "compliant"
501           },
502           {
503             "uuid": "uuid-2",
504             "caycStatus": "over-compliant"
505           }
506         ]
507       }
508       """
509     );
510   }
511
512   @Test
513   public void writes_all_branches() {
514     TelemetryData data = telemetryBuilder()
515       .setBranches(attachBranches())
516       .build();
517
518     String json = writeTelemetryData(data);
519     assertJson(json).isSimilarTo("""
520       {
521         "branches": [
522           {
523             "projectUuid": "projectUuid1",
524             "branchUuid": "branchUuid1",
525             "ncdId": 12345,
526             "greenQualityGateCount": 1,
527             "analysisCount": 2,
528             "excludeFromPurge": true
529           },
530           {
531             "projectUuid": "projectUuid2",
532             "branchUuid": "branchUuid2",
533             "ncdId": 12345,
534             "greenQualityGateCount": 0,
535             "analysisCount": 2,
536             "excludeFromPurge": true
537           }
538         ]
539       }
540       """);
541   }
542
543   @Test
544   public void writes_new_code_definitions() {
545     TelemetryData data = telemetryBuilder()
546       .setNewCodeDefinitions(attachNewCodeDefinitions())
547       .build();
548
549     String json = writeTelemetryData(data);
550     assertJson(json).isSimilarTo("""
551       {
552         "new-code-definitions": [
553           {
554             "ncdId": %s,
555             "type": "%s",
556             "value": "%s",
557             "scope": "%s"
558           },
559           {
560             "ncdId": %s,
561             "type": "%s",
562             "value": "%s",
563             "scope": "%s"
564           },
565         ]
566
567       }
568       """.formatted(NCD_INSTANCE.hashCode(), NCD_INSTANCE.type(), NCD_INSTANCE.value(), NCD_INSTANCE.scope(), NCD_PROJECT.hashCode(),
569       NCD_PROJECT.type(), NCD_PROJECT.value(), NCD_PROJECT.scope()));
570   }
571
572   @Test
573   public void writes_instance_new_code_definition() {
574     TelemetryData data = telemetryBuilder().build();
575
576     String json = writeTelemetryData(data);
577     assertThat(json).contains("ncdId");
578
579   }
580
581   private static TelemetryData.Builder telemetryBuilder() {
582     return TelemetryData.builder()
583       .setServerId("foo")
584       .setVersion("bar")
585       .setMessageSequenceNumber(1L)
586       .setPlugins(Collections.emptyMap())
587       .setManagedInstanceInformation(new TelemetryData.ManagedInstanceInformation(false, null))
588       .setCloudUsage(new TelemetryData.CloudUsage(false))
589       .setDatabase(new TelemetryData.Database("H2", "11"))
590       .setNcdId(NCD_ID);
591   }
592
593   @NotNull
594   private static List<UserTelemetryDto> attachUsers() {
595     return IntStream.range(0, 3)
596       .mapToObj(
597         i -> new UserTelemetryDto().setUuid("uuid-" + i).setActive(i % 2 == 0).setLastConnectionDate(1L)
598           .setLastSonarlintConnectionDate(2L).setExternalIdentityProvider("gitlab").setScimUuid(i % 2 == 0 ? "scim-uuid-" + i : null))
599       .collect(Collectors.toList());
600   }
601
602   private static List<TelemetryData.Project> attachProjects() {
603     return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).collect(Collectors.toList());
604   }
605
606   private static List<TelemetryData.ProjectStatistics> attachProjectStatsWithMetrics() {
607     return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsWithMetricBuilder(i).build()).toList();
608   }
609
610   private static List<TelemetryData.ProjectStatistics> attachProjectStats() {
611     return IntStream.range(0, 3).mapToObj(i -> getProjectStatisticsBuilder(i).build()).toList();
612   }
613
614   private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsBuilder(int i) {
615     return new TelemetryData.ProjectStatistics.Builder()
616       .setProjectUuid("uuid-" + i)
617       .setBranchCount((i + 1L) * 2L)
618       .setPRCount((i + 1L) * 2L)
619       .setQG("qg-" + i).setCi("ci-" + i)
620       .setScm("scm-" + i)
621       .setDevops("devops-" + i)
622       .setNcdId(NCD_ID);
623   }
624
625   private static TelemetryData.ProjectStatistics.Builder getProjectStatisticsWithMetricBuilder(int i) {
626     return getProjectStatisticsBuilder(i)
627       .setBugs((i + 1L) * 2)
628       .setVulnerabilities((i + 1L) * 3)
629       .setSecurityHotspots((i + 1L) * 4)
630       .setDevelopmentCost((i + 1L) * 30d)
631       .setTechnicalDebt((i + 1L) * 60d);
632   }
633
634   private List<TelemetryData.QualityGate> attachQualityGates() {
635     return List.of(new TelemetryData.QualityGate("uuid-0", "non-compliant"),
636       new TelemetryData.QualityGate("uuid-1", "compliant"),
637       new TelemetryData.QualityGate("uuid-2", "over-compliant"));
638   }
639
640   private List<TelemetryData.Branch> attachBranches() {
641     return List.of(new TelemetryData.Branch("projectUuid1", "branchUuid1", NCD_ID, 1, 2, true),
642       new TelemetryData.Branch("projectUuid2", "branchUuid2", NCD_ID, 0, 2, true));
643   }
644
645   private List<TelemetryData.NewCodeDefinition> attachNewCodeDefinitions() {
646     return List.of(NCD_INSTANCE, NCD_PROJECT);
647   }
648
649   @DataProvider
650   public static Object[][] allEditions() {
651     return Arrays.stream(EditionProvider.Edition.values())
652       .map(t -> new Object[] {t})
653       .toArray(Object[][]::new);
654   }
655
656   private String writeTelemetryData(TelemetryData data) {
657     StringWriter jsonString = new StringWriter();
658     try (JsonWriter json = JsonWriter.of(jsonString)) {
659       underTest.writeTelemetryData(json, data);
660     }
661     return jsonString.toString();
662   }
663
664   @DataProvider
665   public static Set<Boolean> getFeatureFlagEnabledStates() {
666     return Set.of(true, false);
667   }
668 }