3 * Copyright (C) 2009-2023 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.telemetry;
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;
31 import java.util.Random;
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;
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;
55 @RunWith(DataProviderRunner.class)
56 public class TelemetryDataJsonWriterTest {
58 private final Random random = new Random();
60 private final TelemetryExtension extension = mock(TelemetryExtension.class);
62 private final System2 system2 = mock(System2.class);
64 private final TelemetryDataJsonWriter underTest = new TelemetryDataJsonWriter(List.of(extension), system2);
67 public void write_server_id_version_and_sequence() {
68 TelemetryData data = telemetryBuilder().build();
70 String json = writeTelemetryData(data);
72 assertJson(json).isSimilarTo("{" +
73 " \"id\": \"" + data.getServerId() + "\"," +
74 " \"version\": \"" + data.getVersion() + "\"," +
75 " \"messageSequenceNumber\": " + data.getMessageSequenceNumber() +
80 public void does_not_write_edition_if_null() {
81 TelemetryData data = telemetryBuilder().build();
83 String json = writeTelemetryData(data);
85 assertThat(json).doesNotContain("edition");
89 @UseDataProvider("allEditions")
90 public void writes_edition_if_non_null(EditionProvider.Edition edition) {
91 TelemetryData data = telemetryBuilder()
95 String json = writeTelemetryData(data);
97 assertJson(json).isSimilarTo("{" +
98 " \"edition\": \"" + edition.name().toLowerCase(Locale.ENGLISH) + "\"" +
103 public void writes_database() {
104 String name = randomAlphabetic(12);
105 String version = randomAlphabetic(10);
106 TelemetryData data = telemetryBuilder()
107 .setDatabase(new TelemetryData.Database(name, version))
110 String json = writeTelemetryData(data);
112 assertJson(json).isSimilarTo("{" +
114 " \"name\": \"" + name + "\"," +
115 " \"version\": \"" + version + "\"" +
121 public void writes_no_plugins() {
122 TelemetryData data = telemetryBuilder()
123 .setPlugins(Collections.emptyMap())
126 String json = writeTelemetryData(data);
128 assertJson(json).isSimilarTo("{" +
134 public void writes_all_plugins() {
135 Map<String, String> plugins = IntStream.range(0, 1 + random.nextInt(10))
137 .collect(MoreCollectors.uniqueIndex(i -> "P" + i, i -> "V" + i));
138 TelemetryData data = telemetryBuilder()
142 String json = writeTelemetryData(data);
144 assertJson(json).isSimilarTo("{" +
147 plugins.entrySet().stream().map(e -> "{\"name\":\"" + e.getKey() + "\",\"version\":\"" + e.getValue() + "\"}").collect(joining(",")) +
153 public void does_not_write_installation_date_if_null() {
154 TelemetryData data = telemetryBuilder()
155 .setInstallationDate(null)
158 String json = writeTelemetryData(data);
160 assertThat(json).doesNotContain("installationDate");
164 public void write_installation_date_in_utc_format() {
165 TelemetryData data = telemetryBuilder()
166 .setInstallationDate(1_000L)
169 String json = writeTelemetryData(data);
171 assertJson(json).isSimilarTo("{" +
172 " \"installationDate\":\"1970-01-01T00:00:01+0000\"," +
177 public void does_not_write_installation_version_if_null() {
178 TelemetryData data = telemetryBuilder()
179 .setInstallationVersion(null)
182 String json = writeTelemetryData(data);
184 assertThat(json).doesNotContain("installationVersion");
188 public void write_installation_version() {
189 String installationVersion = randomAlphabetic(5);
190 TelemetryData data = telemetryBuilder()
191 .setInstallationVersion(installationVersion)
194 String json = writeTelemetryData(data);
196 assertJson(json).isSimilarTo("{" +
197 " \"installationVersion\":\"" + installationVersion + "\"" +
202 @UseDataProvider("getFeatureFlagEnabledStates")
203 public void write_docker_flag(boolean isInDocker) {
204 TelemetryData data = telemetryBuilder()
205 .setInDocker(isInDocker)
208 String json = writeTelemetryData(data);
210 assertJson(json).isSimilarTo("{" +
211 " \"docker\":" + isInDocker +
216 @UseDataProvider("getFeatureFlagEnabledStates")
217 public void write_scim_feature_flag(boolean isScimEnabled) {
218 TelemetryData data = telemetryBuilder()
219 .setIsScimEnabled(isScimEnabled)
222 String json = writeTelemetryData(data);
224 assertJson(json).isSimilarTo("{" + format(" \"%s\":", SCIM_PROPERTY) + isScimEnabled + "}");
228 public void writes_has_unanalyzed_languages() {
229 TelemetryData data = telemetryBuilder()
230 .setHasUnanalyzedC(true)
231 .setHasUnanalyzedCpp(false)
234 String json = writeTelemetryData(data);
236 assertJson(json).isSimilarTo("""
238 "hasUnanalyzedC": true,
239 "hasUnanalyzedCpp": false,
245 public void writes_security_custom_config() {
246 TelemetryData data = telemetryBuilder()
247 .setCustomSecurityConfigs(Set.of("php", "java"))
250 String json = writeTelemetryData(data);
252 assertJson(json).isSimilarTo("{" +
253 " \"customSecurityConfig\": [\"php\", \"java\"]" +
258 public void writes_local_timestamp() {
259 when(system2.now()).thenReturn(1000L);
261 TelemetryData data = telemetryBuilder().build();
262 String json = writeTelemetryData(data);
264 assertJson(json).isSimilarTo("{" +
265 " \"localTimestamp\": \"1970-01-01T00:00:01+0000\"" +
270 public void writes_all_users_with_anonymous_md5_uuids() {
271 TelemetryData data = telemetryBuilder()
272 .setUsers(attachUsers())
275 String json = writeTelemetryData(data);
277 assertJson(json).isSimilarTo("{" +
280 " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-0") + "\"," +
281 " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," +
282 " \"identityProvider\":\"gitlab\"," +
283 " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," +
284 " \"status\":\"active\"" +
287 " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-1") + "\"," +
288 " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," +
289 " \"identityProvider\":\"gitlab\"," +
290 " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," +
291 " \"status\":\"inactive\"" +
294 " \"userUuid\":\"" + DigestUtils.sha3_224Hex("uuid-2") + "\"," +
295 " \"lastActivity\":\"1970-01-01T00:00:00+0000\"," +
296 " \"identityProvider\":\"gitlab\"," +
297 " \"lastSonarlintActivity\":\"1970-01-01T00:00:00+0000\"," +
298 " \"status\":\"active\"" +
305 public void writes_all_projects() {
306 TelemetryData data = telemetryBuilder()
307 .setProjects(attachProjects())
310 String json = writeTelemetryData(data);
312 assertJson(json).isSimilarTo("{" +
315 " \"projectUuid\": \"uuid-0\"," +
316 " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," +
317 " \"language\": \"lang-0\"," +
321 " \"projectUuid\": \"uuid-1\"," +
322 " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," +
323 " \"language\": \"lang-1\"," +
327 " \"projectUuid\": \"uuid-2\"," +
328 " \"lastAnalysis\":\"1970-01-01T00:00:00+0000\"," +
329 " \"language\": \"lang-2\"," +
337 public void writes_all_projects_stats_with_analyzed_languages() {
338 TelemetryData data = telemetryBuilder()
339 .setProjectStatistics(attachProjectStats())
342 String json = writeTelemetryData(data);
344 assertJson(json).isSimilarTo("""
346 "projects-general-stats": [
348 "projectUuid": "uuid-0",
350 "pullRequestCount": 2,
353 "devopsPlatform": "devops-0"
356 "projectUuid": "uuid-1",
358 "pullRequestCount": 4,
361 "devopsPlatform": "devops-1"
364 "projectUuid": "uuid-2",
366 "pullRequestCount": 6,
369 "devopsPlatform": "devops-2"
378 public void writes_all_projects_stats_with_unanalyzed_languages() {
379 TelemetryData data = telemetryBuilder()
380 .setProjectStatistics(attachProjectStats())
383 String json = writeTelemetryData(data);
384 assertThat(json).doesNotContain("hasUnanalyzedC", "hasUnanalyzedCpp");
387 private static TelemetryData.Builder telemetryBuilder() {
388 return TelemetryData.builder()
391 .setMessageSequenceNumber(1L)
392 .setPlugins(Collections.emptyMap())
393 .setDatabase(new TelemetryData.Database("H2", "11"));
397 private static List<UserTelemetryDto> attachUsers() {
398 return IntStream.range(0, 3)
400 i -> new UserTelemetryDto().setUuid("uuid-" + i).setActive(i % 2 == 0).setLastConnectionDate(1L).setLastSonarlintConnectionDate(2L).setExternalIdentityProvider("gitlab"))
401 .collect(Collectors.toList());
404 private static List<TelemetryData.Project> attachProjects() {
405 return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.Project("uuid-" + i, 1L, "lang-" + i, (i + 1L) * 2L)).collect(Collectors.toList());
408 private List<TelemetryData.ProjectStatistics> attachProjectStats() {
409 return IntStream.range(0, 3).mapToObj(i -> new TelemetryData.ProjectStatistics("uuid-" + i, (i + 1L) * 2L, (i + 1L) * 2L, "scm-" + i, "ci-" + i, "devops-" + i))
410 .collect(Collectors.toList());
414 public static Object[][] allEditions() {
415 return Arrays.stream(EditionProvider.Edition.values())
416 .map(t -> new Object[] {t})
417 .toArray(Object[][]::new);
420 private String writeTelemetryData(TelemetryData data) {
421 StringWriter jsonString = new StringWriter();
422 try (JsonWriter json = JsonWriter.of(jsonString)) {
423 underTest.writeTelemetryData(json, data);
425 return jsonString.toString();
429 public static Set<Boolean> getFeatureFlagEnabledStates() {
430 return Set.of(true, false);