Migrate from `Object[]` parameters to `Stream<Arguments>`

This commit is contained in:
Chris Eager 2022-01-03 13:57:16 -08:00 committed by Chris Eager
parent f45a1c232f
commit bb27dd0c3b
6 changed files with 440 additions and 393 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -20,112 +20,121 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
class ExperimentTest { class ExperimentTest {
private Timer matchTimer; private Timer matchTimer;
private Timer errorTimer; private Timer errorTimer;
private Experiment experiment; private Experiment experiment;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
matchTimer = mock(Timer.class); matchTimer = mock(Timer.class);
errorTimer = mock(Timer.class); errorTimer = mock(Timer.class);
experiment = new Experiment("test", matchTimer, errorTimer, mock(Timer.class), mock(Timer.class), mock(Timer.class)); experiment = new Experiment("test", matchTimer, errorTimer, mock(Timer.class), mock(Timer.class),
} mock(Timer.class));
}
@Test @Test
void compareFutureResult() { void compareFutureResult() {
experiment.compareFutureResult(12, CompletableFuture.completedFuture(12)); experiment.compareFutureResult(12, CompletableFuture.completedFuture(12));
verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@Test @Test
void compareFutureResultError() { void compareFutureResultError() {
experiment.compareFutureResult(12, CompletableFuture.failedFuture(new RuntimeException("OH NO"))); experiment.compareFutureResult(12, CompletableFuture.failedFuture(new RuntimeException("OH NO")));
verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@Test @Test
void compareSupplierResultMatch() { void compareSupplierResultMatch() {
experiment.compareSupplierResult(12, () -> 12); experiment.compareSupplierResult(12, () -> 12);
verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@Test @Test
void compareSupplierResultError() { void compareSupplierResultError() {
experiment.compareSupplierResult(12, () -> { throw new RuntimeException("OH NO"); }); experiment.compareSupplierResult(12, () -> {
verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); throw new RuntimeException("OH NO");
} });
verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
}
@Test @Test
void compareSupplierResultAsyncMatch() throws InterruptedException { void compareSupplierResultAsyncMatch() throws InterruptedException {
final ExecutorService experimentExecutor = Executors.newSingleThreadExecutor(); final ExecutorService experimentExecutor = Executors.newSingleThreadExecutor();
experiment.compareSupplierResultAsync(12, () -> 12, experimentExecutor); experiment.compareSupplierResultAsync(12, () -> 12, experimentExecutor);
experimentExecutor.shutdown(); experimentExecutor.shutdown();
experimentExecutor.awaitTermination(1, TimeUnit.SECONDS); experimentExecutor.awaitTermination(1, TimeUnit.SECONDS);
verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(matchTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@Test @Test
void compareSupplierResultAsyncError() throws InterruptedException { void compareSupplierResultAsyncError() throws InterruptedException {
final ExecutorService experimentExecutor = Executors.newSingleThreadExecutor(); final ExecutorService experimentExecutor = Executors.newSingleThreadExecutor();
experiment.compareSupplierResultAsync(12, () -> { throw new RuntimeException("OH NO"); }, experimentExecutor); experiment.compareSupplierResultAsync(12, () -> {
experimentExecutor.shutdown(); throw new RuntimeException("OH NO");
experimentExecutor.awaitTermination(1, TimeUnit.SECONDS); }, experimentExecutor);
experimentExecutor.shutdown();
experimentExecutor.awaitTermination(1, TimeUnit.SECONDS);
verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@Test @Test
void compareSupplierResultAsyncRejection() { void compareSupplierResultAsyncRejection() {
final ExecutorService executorService = mock(ExecutorService.class); final ExecutorService executorService = mock(ExecutorService.class);
doThrow(new RejectedExecutionException()).when(executorService).execute(any(Runnable.class)); doThrow(new RejectedExecutionException()).when(executorService).execute(any(Runnable.class));
experiment.compareSupplierResultAsync(12, () -> 12, executorService); experiment.compareSupplierResultAsync(12, () -> 12, executorService);
verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS)); verify(errorTimer).record(anyLong(), eq(TimeUnit.NANOSECONDS));
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestRecordResult") @MethodSource
public void testRecordResult(final Object expected, final Object actual, final Experiment experiment, final Timer expectedTimer) { public void testRecordResult(final Object expected, final Object actual, final Experiment experiment,
reset(expectedTimer); final Timer expectedTimer) {
reset(expectedTimer);
final long durationNanos = 123; final long durationNanos = 123;
experiment.recordResult(expected, actual, durationNanos); experiment.recordResult(expected, actual, durationNanos);
verify(expectedTimer).record(durationNanos, TimeUnit.NANOSECONDS); verify(expectedTimer).record(durationNanos, TimeUnit.NANOSECONDS);
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static Object[] argumentsForTestRecordResult() { private static Stream<Arguments> testRecordResult() {
// Hack: parameters are set before the @Before method gets called // Hack: parameters are set before the @Before method gets called
final Timer matchTimer = mock(Timer.class); final Timer matchTimer = mock(Timer.class);
final Timer errorTimer = mock(Timer.class); final Timer errorTimer = mock(Timer.class);
final Timer bothPresentMismatchTimer = mock(Timer.class); final Timer bothPresentMismatchTimer = mock(Timer.class);
final Timer controlNullMismatchTimer = mock(Timer.class); final Timer controlNullMismatchTimer = mock(Timer.class);
final Timer experimentNullMismatchTimer = mock(Timer.class); final Timer experimentNullMismatchTimer = mock(Timer.class);
final Experiment experiment = new Experiment("test", matchTimer, errorTimer, bothPresentMismatchTimer, controlNullMismatchTimer, experimentNullMismatchTimer); final Experiment experiment = new Experiment("test", matchTimer, errorTimer, bothPresentMismatchTimer,
controlNullMismatchTimer, experimentNullMismatchTimer);
return new Object[] { return Stream.of(
new Object[] { 12, 12, experiment, matchTimer }, Arguments.of(12, 12, experiment, matchTimer),
new Object[] { null, 12, experiment, controlNullMismatchTimer }, Arguments.of(null, 12, experiment, controlNullMismatchTimer),
new Object[] { 12, null, experiment, experimentNullMismatchTimer }, Arguments.of(12, null, experiment, experimentNullMismatchTimer),
new Object[] { 12, 17, experiment, bothPresentMismatchTimer }, Arguments.of(12, 17, experiment, bothPresentMismatchTimer),
new Object[] { Optional.of(12), Optional.of(12), experiment, matchTimer }, Arguments.of(Optional.of(12), Optional.of(12), experiment, matchTimer),
new Object[] { Optional.empty(), Optional.of(12), experiment, controlNullMismatchTimer }, Arguments.of(Optional.empty(), Optional.of(12), experiment, controlNullMismatchTimer),
new Object[] { Optional.of(12), Optional.empty(), experiment, experimentNullMismatchTimer }, Arguments.of(Optional.of(12), Optional.empty(), experiment, experimentNullMismatchTimer),
new Object[] { Optional.of(12), Optional.of(17), experiment, bothPresentMismatchTimer } Arguments.of(Optional.of(12), Optional.of(17), experiment, bothPresentMismatchTimer)
}; );
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2021 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -8,81 +8,84 @@ package org.whispersystems.textsecuregcm.metrics;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
class OperatingSystemMemoryGaugeTest { class OperatingSystemMemoryGaugeTest {
private static final String MEMINFO = private static final String MEMINFO =
""" """
MemTotal: 16052208 kB MemTotal: 16052208 kB
MemFree: 4568468 kB MemFree: 4568468 kB
MemAvailable: 7702848 kB MemAvailable: 7702848 kB
Buffers: 636372 kB Buffers: 636372 kB
Cached: 5019116 kB Cached: 5019116 kB
SwapCached: 6692 kB SwapCached: 6692 kB
Active: 7746436 kB Active: 7746436 kB
Inactive: 2729876 kB Inactive: 2729876 kB
Active(anon): 5580980 kB Active(anon): 5580980 kB
Inactive(anon): 1648108 kB Inactive(anon): 1648108 kB
Active(file): 2165456 kB Active(file): 2165456 kB
Inactive(file): 1081768 kB Inactive(file): 1081768 kB
Unevictable: 443948 kB Unevictable: 443948 kB
Mlocked: 4924 kB Mlocked: 4924 kB
SwapTotal: 1003516 kB SwapTotal: 1003516 kB
SwapFree: 935932 kB SwapFree: 935932 kB
Dirty: 28308 kB Dirty: 28308 kB
Writeback: 0 kB Writeback: 0 kB
AnonPages: 5258396 kB AnonPages: 5258396 kB
Mapped: 1530740 kB Mapped: 1530740 kB
Shmem: 2419340 kB Shmem: 2419340 kB
KReclaimable: 229392 kB KReclaimable: 229392 kB
Slab: 408156 kB Slab: 408156 kB
SReclaimable: 229392 kB SReclaimable: 229392 kB
SUnreclaim: 178764 kB SUnreclaim: 178764 kB
KernelStack: 17360 kB KernelStack: 17360 kB
PageTables: 50436 kB PageTables: 50436 kB
NFS_Unstable: 0 kB NFS_Unstable: 0 kB
Bounce: 0 kB Bounce: 0 kB
WritebackTmp: 0 kB WritebackTmp: 0 kB
CommitLimit: 9029620 kB CommitLimit: 9029620 kB
Committed_AS: 16681884 kB Committed_AS: 16681884 kB
VmallocTotal: 34359738367 kB VmallocTotal: 34359738367 kB
VmallocUsed: 41944 kB VmallocUsed: 41944 kB
VmallocChunk: 0 kB VmallocChunk: 0 kB
Percpu: 4240 kB Percpu: 4240 kB
HardwareCorrupted: 0 kB HardwareCorrupted: 0 kB
AnonHugePages: 0 kB AnonHugePages: 0 kB
ShmemHugePages: 0 kB ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB ShmemPmdMapped: 0 kB
FileHugePages: 0 kB FileHugePages: 0 kB
FilePmdMapped: 0 kB FilePmdMapped: 0 kB
CmaTotal: 0 kB CmaTotal: 0 kB
CmaFree: 0 kB CmaFree: 0 kB
HugePages_Total: 0 HugePages_Total: 0
HugePages_Free: 7 HugePages_Free: 7
HugePages_Rsvd: 0 HugePages_Rsvd: 0
HugePages_Surp: 0 HugePages_Surp: 0
Hugepagesize: 2048 kB Hugepagesize: 2048 kB
Hugetlb: 0 kB Hugetlb: 0 kB
DirectMap4k: 481804 kB DirectMap4k: 481804 kB
DirectMap2M: 14901248 kB DirectMap2M: 14901248 kB
DirectMap1G: 2097152 kB DirectMap1G: 2097152 kB
"""; """;
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestGetValue") @MethodSource
public void testGetValue(final String metricName, final long expectedValue) { void testGetValue(final String metricName, final long expectedValue) {
assertEquals(expectedValue, new OperatingSystemMemoryGauge(metricName).getValue(MEMINFO.lines())); assertEquals(expectedValue, new OperatingSystemMemoryGauge(metricName).getValue(MEMINFO.lines()));
} }
private static Object[] argumentsForTestGetValue() { @SuppressWarnings("unused")
return new Object[] { private static Stream<Arguments> testGetValue() {
new Object[] { "MemTotal", 16052208L }, return Stream.of(
new Object[] { "Active(anon)", 5580980L }, Arguments.of("MemTotal", 16052208L),
new Object[] { "Committed_AS", 16681884L }, Arguments.of("Active(anon)", 5580980L),
new Object[] { "HugePages_Free", 7L }, Arguments.of("Committed_AS", 16681884L),
new Object[] { "NonsenseMetric", 0L } Arguments.of("HugePages_Free", 7L),
}; Arguments.of("NonsenseMetric", 0L)
} );
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -11,81 +11,85 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
class UserAgentTagUtilTest { class UserAgentTagUtilTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestGetUserAgentTags") @MethodSource
public void testGetUserAgentTags(final String userAgent, final List<Tag> expectedTags) { public void testGetUserAgentTags(final String userAgent, final List<Tag> expectedTags) {
assertEquals(new HashSet<>(expectedTags), assertEquals(new HashSet<>(expectedTags),
new HashSet<>(UserAgentTagUtil.getUserAgentTags(userAgent))); new HashSet<>(UserAgentTagUtil.getUserAgentTags(userAgent)));
}
private static List<Tag> platformVersionTags(String platform, String version) {
return List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, platform), Tag.of(UserAgentTagUtil.VERSION_TAG, version));
}
@SuppressWarnings("unused")
private static Stream<Arguments> testGetUserAgentTags() {
return Stream.of(
Arguments.of("This is obviously not a reasonable User-Agent string.", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of(null, UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal-Android 4.53.7 (Android 8.1)", platformVersionTags("android", "4.53.7")),
Arguments.of("Signal Desktop 1.2.3", platformVersionTags("desktop", "1.2.3")),
Arguments.of("Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "3.9.0")),
Arguments.of("Signal-Android 1.2.3 (Android 8.1)", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal Desktop 3.9.0", platformVersionTags("desktop", "3.9.0")),
Arguments.of("Signal/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "4.53.7")),
Arguments.of("Signal-Android 4.68.3 (Android 9)", platformVersionTags("android", "4.68.3")),
Arguments.of("Signal-Android 1.2.3 (Android 4.3)", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal-Android 4.68.3.0-bobsbootlegclient", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal Desktop 1.22.45-foo-0", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal Desktop 1.34.5-beta.1-fakeclientemporium", UserAgentTagUtil.UNRECOGNIZED_TAGS),
Arguments.of("Signal Desktop 1.32.0-beta.3", UserAgentTagUtil.UNRECOGNIZED_TAGS)
);
}
@Test
void testGetUserAgentTagsFlooded() {
for (int i = 0; i < UserAgentTagUtil.MAX_VERSIONS; i++) {
UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android 4.0.%d (Android 8.1)", i));
} }
private static List<Tag> platformVersionTags(String platform, String version) { assertEquals(UserAgentTagUtil.OVERFLOW_TAGS,
return List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, platform), Tag.of(UserAgentTagUtil.VERSION_TAG, version)); UserAgentTagUtil.getUserAgentTags("Signal-Android 4.1.0 (Android 8.1)"));
}
@SuppressWarnings("unused") final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 4.0.0 (Android 8.1)");
private static Object[] argumentsForTestGetUserAgentTags() {
return new Object[] {
new Object[] { "This is obviously not a reasonable User-Agent string.", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { null, UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal-Android 4.53.7 (Android 8.1)", platformVersionTags("android", "4.53.7") },
new Object[] { "Signal Desktop 1.2.3", platformVersionTags("desktop", "1.2.3") },
new Object[] { "Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "3.9.0") },
new Object[] { "Signal-Android 1.2.3 (Android 8.1)", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal Desktop 3.9.0", platformVersionTags("desktop", "3.9.0") },
new Object[] { "Signal/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", platformVersionTags("ios", "4.53.7") },
new Object[] { "Signal-Android 4.68.3 (Android 9)", platformVersionTags("android", "4.68.3") },
new Object[] { "Signal-Android 1.2.3 (Android 4.3)", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal-Android 4.68.3.0-bobsbootlegclient", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal Desktop 1.22.45-foo-0", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal Desktop 1.34.5-beta.1-fakeclientemporium", UserAgentTagUtil.UNRECOGNIZED_TAGS },
new Object[] { "Signal Desktop 1.32.0-beta.3", UserAgentTagUtil.UNRECOGNIZED_TAGS },
};
}
@Test assertEquals(2, tags.size());
void testGetUserAgentTagsFlooded() { assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
for (int i = 0; i < UserAgentTagUtil.MAX_VERSIONS; i++) { assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.0.0")));
UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android 4.0.%d (Android 8.1)", i)); }
}
assertEquals(UserAgentTagUtil.OVERFLOW_TAGS, @ParameterizedTest
UserAgentTagUtil.getUserAgentTags("Signal-Android 4.1.0 (Android 8.1)")); @MethodSource("argumentsForTestGetPlatformTag")
public void testGetPlatformTag(final String userAgent, final Tag expectedTag) {
assertEquals(expectedTag, UserAgentTagUtil.getPlatformTag(userAgent));
}
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 4.0.0 (Android 8.1)"); private static Stream<Arguments> argumentsForTestGetPlatformTag() {
return Stream.of(
assertEquals(2, tags.size()); Arguments.of("This is obviously not a reasonable User-Agent string.",
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"))); Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")),
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.0.0"))); Arguments.of(null, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized")),
} Arguments.of("Signal-Android 4.53.7 (Android 8.1)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")),
Arguments.of("Signal Desktop 1.2.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
@ParameterizedTest Arguments.of("Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios")),
@MethodSource("argumentsForTestGetPlatformTag") Arguments.of("Signal-Android 1.2.3 (Android 8.1)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")),
public void testGetPlatformTag(final String userAgent, final Tag expectedTag) { Arguments.of("Signal Desktop 3.9.0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
assertEquals(expectedTag, UserAgentTagUtil.getPlatformTag(userAgent)); Arguments.of("Signal/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios")),
} Arguments.of("Signal-Android 4.68.3 (Android 9)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")),
Arguments.of("Signal-Android 1.2.3 (Android 4.3)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")),
private static Object[] argumentsForTestGetPlatformTag() { Arguments.of("Signal-Android 4.68.3.0-bobsbootlegclient", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")),
return new Object[] { Arguments.of("Signal Desktop 1.22.45-foo-0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
new Object[] { "This is obviously not a reasonable User-Agent string.", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized") }, Arguments.of("Signal Desktop 1.34.5-beta.1-fakeclientemporium",
new Object[] { null, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "unrecognized") }, Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")),
new Object[] { "Signal-Android 4.53.7 (Android 8.1)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android") }, Arguments.of("Signal Desktop 1.32.0-beta.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop"))
new Object[] { "Signal Desktop 1.2.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop") }, );
new Object[] { "Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios") }, }
new Object[] { "Signal-Android 1.2.3 (Android 8.1)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android") },
new Object[] { "Signal Desktop 3.9.0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop") },
new Object[] { "Signal/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios") },
new Object[] { "Signal-Android 4.68.3 (Android 9)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android") },
new Object[] { "Signal-Android 1.2.3 (Android 4.3)", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android") },
new Object[] { "Signal-Android 4.68.3.0-bobsbootlegclient", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android") },
new Object[] { "Signal Desktop 1.22.45-foo-0", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop") },
new Object[] { "Signal Desktop 1.34.5-beta.1-fakeclientemporium", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop") },
new Object[] { "Signal Desktop 1.32.0-beta.3", Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop") },
};
}
} }

View File

@ -1,3 +1,8 @@
/*
* Copyright 2021-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.sms; package org.whispersystems.textsecuregcm.sms;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
@ -6,7 +11,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension; import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
@ -16,11 +21,13 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale.LanguageRange; import java.util.Locale.LanguageRange;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient; import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
@ -89,7 +96,7 @@ class TwilioVerifySenderTest {
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForDeliverSmsVerificationWithVerify") @MethodSource
void deliverSmsVerificationWithVerify(@Nullable final String client, @Nullable final String languageRange, void deliverSmsVerificationWithVerify(@Nullable final String client, @Nullable final String languageRange,
final boolean expectAppHash, @Nullable final String expectedLocale) throws Exception { final boolean expectAppHash, @Nullable final String expectedLocale) throws Exception {
@ -114,17 +121,18 @@ class TwilioVerifySenderTest {
))); )));
} }
private static Object[] argumentsForDeliverSmsVerificationWithVerify() { @SuppressWarnings("unused")
return new Object[][]{ private static Stream<Arguments> deliverSmsVerificationWithVerify() {
return Stream.of(
// client, languageRange, expectAppHash, expectedLocale // client, languageRange, expectAppHash, expectedLocale
{"ios", "fr-CA, en", false, "fr"}, Arguments.of("ios", "fr-CA, en", false, "fr"),
{"android-2021-03", "zh-HK, it", true, "zh-HK"}, Arguments.of("android-2021-03", "zh-HK, it", true, "zh-HK"),
{null, null, false, null} Arguments.of(null, null, false, null)
}; );
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForDeliverVoxVerificationWithVerify") @MethodSource
void deliverVoxVerificationWithVerify(@Nullable final String languageRange, void deliverVoxVerificationWithVerify(@Nullable final String languageRange,
@Nullable final String expectedLocale) throws Exception { @Nullable final String expectedLocale) throws Exception {
@ -147,14 +155,15 @@ class TwilioVerifySenderTest {
+ "&CustomCode=123456"))); + "&CustomCode=123456")));
} }
private static Object[] argumentsForDeliverVoxVerificationWithVerify() { @SuppressWarnings("unused")
return new Object[][]{ private static Stream<Arguments> deliverVoxVerificationWithVerify() {
return Stream.of(
// languageRange, expectedLocale // languageRange, expectedLocale
{"fr-CA, en", "fr"}, Arguments.of("fr-CA, en", "fr"),
{"zh-HK, it", "zh-HK"}, Arguments.of("zh-HK, it", "zh-HK"),
{"en-CAA, en", "en"}, Arguments.of("en-CAA, en", "en"),
{null, null} Arguments.of(null, null)
}; );
} }
@Test @Test
@ -203,16 +212,17 @@ class TwilioVerifySenderTest {
wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID)) wireMock.stubFor(post(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN) .withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
.willReturn(aResponse() .willReturn(aResponse()
.withStatus(200) .withStatus(200)
.withHeader("Content-Type", "application/json") .withHeader("Content-Type", "application/json")
.withBody("{\"status\": \"approved\", \"sid\": \"" + VERIFICATION_SID + "\"}"))); .withBody("{\"status\": \"approved\", \"sid\": \"" + VERIFICATION_SID + "\"}")));
final Boolean success = sender.reportVerificationSucceeded(VERIFICATION_SID).get(); final Boolean success = sender.reportVerificationSucceeded(VERIFICATION_SID).get();
assertThat(success).isTrue(); assertThat(success).isTrue();
wireMock.verify(1, postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID)) wireMock.verify(1,
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded")) postRequestedFor(urlEqualTo("/v2/Services/" + VERIFY_SERVICE_SID + "/Verifications/" + VERIFICATION_SID))
.withRequestBody(equalTo("Status=approved"))); .withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
.withRequestBody(equalTo("Status=approved")));
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -9,113 +9,119 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import java.time.Duration; import java.time.Duration;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.entities.SignedPreKey; import org.whispersystems.textsecuregcm.entities.SignedPreKey;
class DeviceTest { class DeviceTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestIsEnabled") @MethodSource
void testIsEnabled(final boolean master, final boolean fetchesMessages, final String apnId, final String gcmId, final SignedPreKey signedPreKey, final Duration timeSinceLastSeen, final boolean expectEnabled) { void testIsEnabled(final boolean master, final boolean fetchesMessages, final String apnId, final String gcmId,
final long lastSeen = System.currentTimeMillis() - timeSinceLastSeen.toMillis(); final SignedPreKey signedPreKey, final Duration timeSinceLastSeen, final boolean expectEnabled) {
final Device device = new Device(master ? 1 : 2, "test", "auth-token", "salt", gcmId, apnId, null, fetchesMessages, 1, signedPreKey, lastSeen, lastSeen, "user-agent", 0, null); final long lastSeen = System.currentTimeMillis() - timeSinceLastSeen.toMillis();
final Device device = new Device(master ? 1 : 2, "test", "auth-token", "salt", gcmId, apnId, null, fetchesMessages,
1, signedPreKey, lastSeen, lastSeen, "user-agent", 0, null);
assertEquals(expectEnabled, device.isEnabled()); assertEquals(expectEnabled, device.isEnabled());
} }
private static Object[] argumentsForTestIsEnabled() { private static Stream<Arguments> testIsEnabled() {
return new Object[] { return Stream.of(
// master fetchesMessages apnId gcmId signedPreKey lastSeen expectEnabled // master fetchesMessages apnId gcmId signedPreKey lastSeen expectEnabled
new Object[] { true, false, null, null, null, Duration.ofDays(60), false }, Arguments.of(true, false, null, null, null, Duration.ofDays(60), false),
new Object[] { true, false, null, null, null, Duration.ofDays(1), false }, Arguments.of(true, false, null, null, null, Duration.ofDays(1), false),
new Object[] { true, false, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false }, Arguments.of(true, false, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false),
new Object[] { true, false, null, null, mock(SignedPreKey.class), Duration.ofDays(1), false }, Arguments.of(true, false, null, null, mock(SignedPreKey.class), Duration.ofDays(1), false),
new Object[] { true, false, null, "gcm-id", null, Duration.ofDays(60), false }, Arguments.of(true, false, null, "gcm-id", null, Duration.ofDays(60), false),
new Object[] { true, false, null, "gcm-id", null, Duration.ofDays(1), false }, Arguments.of(true, false, null, "gcm-id", null, Duration.ofDays(1), false),
new Object[] { true, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(60), true }, Arguments.of(true, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(60), true),
new Object[] { true, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(1), true }, Arguments.of(true, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(1), true),
new Object[] { true, false, "apn-id", null, null, Duration.ofDays(60), false }, Arguments.of(true, false, "apn-id", null, null, Duration.ofDays(60), false),
new Object[] { true, false, "apn-id", null, null, Duration.ofDays(1), false }, Arguments.of(true, false, "apn-id", null, null, Duration.ofDays(1), false),
new Object[] { true, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(60), true }, Arguments.of(true, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(60), true),
new Object[] { true, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(1), true }, Arguments.of(true, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(1), true),
new Object[] { true, true, null, null, null, Duration.ofDays(60), false }, Arguments.of(true, true, null, null, null, Duration.ofDays(60), false),
new Object[] { true, true, null, null, null, Duration.ofDays(1), false }, Arguments.of(true, true, null, null, null, Duration.ofDays(1), false),
new Object[] { true, true, null, null, mock(SignedPreKey.class), Duration.ofDays(60), true }, Arguments.of(true, true, null, null, mock(SignedPreKey.class), Duration.ofDays(60), true),
new Object[] { true, true, null, null, mock(SignedPreKey.class), Duration.ofDays(1), true }, Arguments.of(true, true, null, null, mock(SignedPreKey.class), Duration.ofDays(1), true),
new Object[] { false, false, null, null, null, Duration.ofDays(60), false }, Arguments.of(false, false, null, null, null, Duration.ofDays(60), false),
new Object[] { false, false, null, null, null, Duration.ofDays(1), false }, Arguments.of(false, false, null, null, null, Duration.ofDays(1), false),
new Object[] { false, false, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false }, Arguments.of(false, false, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false),
new Object[] { false, false, null, null, mock(SignedPreKey.class), Duration.ofDays(1), false }, Arguments.of(false, false, null, null, mock(SignedPreKey.class), Duration.ofDays(1), false),
new Object[] { false, false, null, "gcm-id", null, Duration.ofDays(60), false }, Arguments.of(false, false, null, "gcm-id", null, Duration.ofDays(60), false),
new Object[] { false, false, null, "gcm-id", null, Duration.ofDays(1), false }, Arguments.of(false, false, null, "gcm-id", null, Duration.ofDays(1), false),
new Object[] { false, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(60), false }, Arguments.of(false, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(60), false),
new Object[] { false, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(1), true }, Arguments.of(false, false, null, "gcm-id", mock(SignedPreKey.class), Duration.ofDays(1), true),
new Object[] { false, false, "apn-id", null, null, Duration.ofDays(60), false }, Arguments.of(false, false, "apn-id", null, null, Duration.ofDays(60), false),
new Object[] { false, false, "apn-id", null, null, Duration.ofDays(1), false }, Arguments.of(false, false, "apn-id", null, null, Duration.ofDays(1), false),
new Object[] { false, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(60), false }, Arguments.of(false, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(60), false),
new Object[] { false, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(1), true }, Arguments.of(false, false, "apn-id", null, mock(SignedPreKey.class), Duration.ofDays(1), true),
new Object[] { false, true, null, null, null, Duration.ofDays(60), false }, Arguments.of(false, true, null, null, null, Duration.ofDays(60), false),
new Object[] { false, true, null, null, null, Duration.ofDays(1), false }, Arguments.of(false, true, null, null, null, Duration.ofDays(1), false),
new Object[] { false, true, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false }, Arguments.of(false, true, null, null, mock(SignedPreKey.class), Duration.ofDays(60), false),
new Object[] { false, true, null, null, mock(SignedPreKey.class), Duration.ofDays(1), true } Arguments.of(false, true, null, null, mock(SignedPreKey.class), Duration.ofDays(1), true)
}; );
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestIsGroupsV2Supported") @MethodSource("argumentsForTestIsGroupsV2Supported")
void testIsGroupsV2Supported(final boolean master, final String apnId, final boolean gv2Capability, final boolean gv2_2Capability, final boolean gv2_3Capability, final boolean expectGv2Supported) { void testIsGroupsV2Supported(final boolean master, final String apnId, final boolean gv2Capability,
final Device.DeviceCapabilities capabilities = new Device.DeviceCapabilities(gv2Capability, gv2_2Capability, gv2_3Capability, false, false, false, final boolean gv2_2Capability, final boolean gv2_3Capability, final boolean expectGv2Supported) {
false, false, false); final Device.DeviceCapabilities capabilities = new Device.DeviceCapabilities(gv2Capability, gv2_2Capability,
final Device device = new Device(master ? 1 : 2, "test", "auth-token", "salt", gv2_3Capability, false, false, false,
null, apnId, null, false, 1, null, 0, 0, "user-agent", 0, capabilities); false, false, false);
final Device device = new Device(master ? 1 : 2, "test", "auth-token", "salt",
null, apnId, null, false, 1, null, 0, 0, "user-agent", 0, capabilities);
assertEquals(expectGv2Supported, device.isGroupsV2Supported()); assertEquals(expectGv2Supported, device.isGroupsV2Supported());
} }
private static Object[] argumentsForTestIsGroupsV2Supported() { private static Stream<Arguments> argumentsForTestIsGroupsV2Supported() {
return new Object[] { return Stream.of(
// master apnId gv2 gv2-2 gv2-3 capable // master apnId gv2 gv2-2 gv2-3 capable
// Android master // Android master
new Object[] { true, null, false, false, false, false }, Arguments.of(true, null, false, false, false, false),
new Object[] { true, null, true, false, false, false }, Arguments.of(true, null, true, false, false, false),
new Object[] { true, null, false, true, false, false }, Arguments.of(true, null, false, true, false, false),
new Object[] { true, null, true, true, false, false }, Arguments.of(true, null, true, true, false, false),
new Object[] { true, null, false, false, true, true }, Arguments.of(true, null, false, false, true, true),
new Object[] { true, null, true, false, true, true }, Arguments.of(true, null, true, false, true, true),
new Object[] { true, null, false, true, true, true }, Arguments.of(true, null, false, true, true, true),
new Object[] { true, null, true, true, true, true }, Arguments.of(true, null, true, true, true, true),
// iOs master // iOS master
new Object[] { true, "apn-id", false, false, false, false }, Arguments.of(true, "apn-id", false, false, false, false),
new Object[] { true, "apn-id", true, false, false, false }, Arguments.of(true, "apn-id", true, false, false, false),
new Object[] { true, "apn-id", false, true, false, true }, Arguments.of(true, "apn-id", false, true, false, true),
new Object[] { true, "apn-id", true, true, false, true }, Arguments.of(true, "apn-id", true, true, false, true),
new Object[] { true, "apn-id", false, false, true, true }, Arguments.of(true, "apn-id", false, false, true, true),
new Object[] { true, "apn-id", true, false, true, true }, Arguments.of(true, "apn-id", true, false, true, true),
new Object[] { true, "apn-id", false, true, true, true }, Arguments.of(true, "apn-id", false, true, true, true),
new Object[] { true, "apn-id", true, true, true, true }, Arguments.of(true, "apn-id", true, true, true, true),
// iOs linked // iOS linked
new Object[] { false, "apn-id", false, false, false, false }, Arguments.of(false, "apn-id", false, false, false, false),
new Object[] { false, "apn-id", true, false, false, false }, Arguments.of(false, "apn-id", true, false, false, false),
new Object[] { false, "apn-id", false, true, false, true }, Arguments.of(false, "apn-id", false, true, false, true),
new Object[] { false, "apn-id", true, true, false, true }, Arguments.of(false, "apn-id", true, true, false, true),
new Object[] { false, "apn-id", false, false, true, true }, Arguments.of(false, "apn-id", false, false, true, true),
new Object[] { false, "apn-id", true, false, true, true }, Arguments.of(false, "apn-id", true, false, true, true),
new Object[] { false, "apn-id", false, true, true, true }, Arguments.of(false, "apn-id", false, true, true, true),
new Object[] { false, "apn-id", true, true, true, true }, Arguments.of(false, "apn-id", true, true, true, true),
// desktop linked // desktop linked
new Object[] { false, null, false, false, false, false }, Arguments.of(false, null, false, false, false, false),
new Object[] { false, null, true, false, false, false }, Arguments.of(false, null, true, false, false, false),
new Object[] { false, null, false, true, false, false }, Arguments.of(false, null, false, true, false, false),
new Object[] { false, null, true, true, false, false }, Arguments.of(false, null, true, true, false, false),
new Object[] { false, null, false, false, true, true }, Arguments.of(false, null, false, false, true, true),
new Object[] { false, null, true, false, true, true }, Arguments.of(false, null, true, false, true, true),
new Object[] { false, null, false, true, true, true }, Arguments.of(false, null, false, true, true, true),
new Object[] { false, null, true, true, true, true } Arguments.of(false, null, true, true, true, true)
}; );
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2020 Signal Messenger, LLC * Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -9,73 +9,88 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import com.vdurmont.semver4j.Semver; import com.vdurmont.semver4j.Semver;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
class UserAgentUtilTest { class UserAgentUtilTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestParseUserAgentString") @MethodSource
void testParseUserAgentString(final String userAgentString, final UserAgent expectedUserAgent) throws UnrecognizedUserAgentException { void testParseUserAgentString(final String userAgentString, final UserAgent expectedUserAgent)
assertEquals(expectedUserAgent, UserAgentUtil.parseUserAgentString(userAgentString)); throws UnrecognizedUserAgentException {
} assertEquals(expectedUserAgent, UserAgentUtil.parseUserAgentString(userAgentString));
}
private static Object[] argumentsForTestParseUserAgentString() { @SuppressWarnings("unused")
return new Object[] { private static Stream<Arguments> testParseUserAgentString() {
new Object[] { "Signal-Android/4.68.3 Android/25", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25") }, return Stream.of(
new Object[] { "Signal-Android 4.53.7 (Android 8.1)", new UserAgent(ClientPlatform.ANDROID, new Semver("4.53.7"), "(Android 8.1)") }, Arguments.of("Signal-Android/4.68.3 Android/25",
}; new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25")),
} Arguments.of("Signal-Android 4.53.7 (Android 8.1)",
new UserAgent(ClientPlatform.ANDROID, new Semver("4.53.7"), "(Android 8.1)"))
);
}
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestParseBogusUserAgentString") @MethodSource
void testParseBogusUserAgentString(final String userAgentString) { void testParseBogusUserAgentString(final String userAgentString) {
assertThrows(UnrecognizedUserAgentException.class, () -> UserAgentUtil.parseUserAgentString(userAgentString)); assertThrows(UnrecognizedUserAgentException.class, () -> UserAgentUtil.parseUserAgentString(userAgentString));
} }
private static Object[] argumentsForTestParseBogusUserAgentString() { @SuppressWarnings("unused")
return new Object[] { private static Stream<String> testParseBogusUserAgentString() {
null, return Stream.of(
"This is obviously not a reasonable User-Agent string.", null,
"Signal-Android/4.6-8.3.unreasonableversionstring-17" "This is obviously not a reasonable User-Agent string.",
}; "Signal-Android/4.6-8.3.unreasonableversionstring-17"
} );
}
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestParseStandardUserAgentString") @MethodSource("argumentsForTestParseStandardUserAgentString")
void testParseStandardUserAgentString(final String userAgentString, final UserAgent expectedUserAgent) { void testParseStandardUserAgentString(final String userAgentString, final UserAgent expectedUserAgent) {
assertEquals(expectedUserAgent, UserAgentUtil.parseStandardUserAgentString(userAgentString)); assertEquals(expectedUserAgent, UserAgentUtil.parseStandardUserAgentString(userAgentString));
} }
private static Object[] argumentsForTestParseStandardUserAgentString() { private static Stream<Arguments> argumentsForTestParseStandardUserAgentString() {
return new Object[] { return Stream.of(
new Object[] { "This is obviously not a reasonable User-Agent string.", null }, Arguments.of("This is obviously not a reasonable User-Agent string.", null),
new Object[] { "Signal-Android/4.68.3 Android/25", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25") }, Arguments.of("Signal-Android/4.68.3 Android/25",
new Object[] { "Signal-Android/4.68.3", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3")) }, new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"), "Android/25")),
new Object[] { "Signal-Desktop/1.2.3 Linux", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux") }, Arguments.of("Signal-Android/4.68.3", new UserAgent(ClientPlatform.ANDROID, new Semver("4.68.3"))),
new Object[] { "Signal-Desktop/1.2.3 macOS", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "macOS") }, Arguments.of("Signal-Desktop/1.2.3 Linux", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Linux")),
new Object[] { "Signal-Desktop/1.2.3 Windows", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Windows") }, Arguments.of("Signal-Desktop/1.2.3 macOS", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "macOS")),
new Object[] { "Signal-Desktop/1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3")) }, Arguments.of("Signal-Desktop/1.2.3 Windows",
new Object[] { "Signal-Desktop/1.32.0-beta.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3")) }, new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"), "Windows")),
new Object[] { "Signal-iOS/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)") }, Arguments.of("Signal-Desktop/1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"))),
new Object[] { "Signal-iOS/3.9.0 iOS/14.2", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "iOS/14.2") }, Arguments.of("Signal-Desktop/1.32.0-beta.3",
new Object[] { "Signal-iOS/3.9.0", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0")) } new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3"))),
}; Arguments.of("Signal-iOS/3.9.0 (iPhone; iOS 12.2; Scale/3.00)",
} new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)")),
Arguments.of("Signal-iOS/3.9.0 iOS/14.2", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "iOS/14.2")),
Arguments.of("Signal-iOS/3.9.0", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0")))
);
}
@ParameterizedTest @ParameterizedTest
@MethodSource("argumentsForTestParseLegacyUserAgentString") @MethodSource
void testParseLegacyUserAgentString(final String userAgentString, final UserAgent expectedUserAgent) { void testParseLegacyUserAgentString(final String userAgentString, final UserAgent expectedUserAgent) {
assertEquals(expectedUserAgent, UserAgentUtil.parseLegacyUserAgentString(userAgentString)); assertEquals(expectedUserAgent, UserAgentUtil.parseLegacyUserAgentString(userAgentString));
} }
private static Object[] argumentsForTestParseLegacyUserAgentString() { @SuppressWarnings("unused")
return new Object[] { private static Stream<Arguments> testParseLegacyUserAgentString() {
new Object[] { "This is obviously not a reasonable User-Agent string.", null }, return Stream.of(
new Object[] { "Signal-Android 4.53.7 (Android 8.1)", new UserAgent(ClientPlatform.ANDROID, new Semver("4.53.7"), "(Android 8.1)") }, Arguments.of("This is obviously not a reasonable User-Agent string.", null),
new Object[] { "Signal Desktop 1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3")) }, Arguments.of("Signal-Android 4.53.7 (Android 8.1)",
new Object[] { "Signal Desktop 1.32.0-beta.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3")) }, new UserAgent(ClientPlatform.ANDROID, new Semver("4.53.7"), "(Android 8.1)")),
new Object[] { "Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)") } Arguments.of("Signal Desktop 1.2.3", new UserAgent(ClientPlatform.DESKTOP, new Semver("1.2.3"))),
}; Arguments.of("Signal Desktop 1.32.0-beta.3",
} new UserAgent(ClientPlatform.DESKTOP, new Semver("1.32.0-beta.3"))),
Arguments.of("Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)",
new UserAgent(ClientPlatform.IOS, new Semver("3.9.0"), "(iPhone; iOS 12.2; Scale/3.00)"))
);
}
} }