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