Add tests for WhisperServerService#run

Additionally, `LocalWhisperServerService` may be used for integration testing.
This commit is contained in:
Chris Eager
2024-04-29 11:05:35 -05:00
committed by GitHub
parent b6f8bca361
commit 0e4be0c85a
84 changed files with 2156 additions and 552 deletions

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@JsonTypeName("local")
public class LocalDynamoDbFactory implements DynamoDbClientFactory {
private static final DynamoDbExtension EXTENSION = new DynamoDbExtension(System.getProperty("sqlite.dir"),
DynamoDbExtensionSchema.Tables.values());
static {
try {
EXTENSION.beforeEach(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> EXTENSION.afterEach(null)));
}
@Override
public DynamoDbClient buildSyncClient(final AwsCredentialsProvider awsCredentialsProvider) {
return EXTENSION.getDynamoDbClient();
}
@Override
public DynamoDbAsyncClient buildAsyncClient(final AwsCredentialsProvider awsCredentialsProvider) {
return EXTENSION.getDynamoDbAsyncClient();
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.lettuce.core.resource.ClientResources;
import java.util.concurrent.atomic.AtomicBoolean;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
@JsonTypeName("local")
public class LocalFaultTolerantRedisClusterFactory implements FaultTolerantRedisClusterFactory {
private static final RedisClusterExtension redisClusterExtension = RedisClusterExtension.builder().build();
private final AtomicBoolean shutdownHookConfigured = new AtomicBoolean();
private LocalFaultTolerantRedisClusterFactory() {
try {
redisClusterExtension.beforeAll(null);
redisClusterExtension.beforeEach(null);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
@Override
public FaultTolerantRedisCluster build(final String name, final ClientResources.Builder clientResourcesBuilder) {
if (shutdownHookConfigured.compareAndSet(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
redisClusterExtension.afterEach(null);
redisClusterExtension.afterAll(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
}
final RedisClusterConfiguration config = new RedisClusterConfiguration();
config.setConfigurationUri(RedisClusterExtension.getRedisURIs().getFirst().toString());
return new FaultTolerantRedisCluster(name, config, clientResourcesBuilder);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.dropwizard.lifecycle.Managed;
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
import java.util.concurrent.atomic.AtomicBoolean;
import org.whispersystems.textsecuregcm.redis.RedisSingletonExtension;
@JsonTypeName("local")
public class LocalSingletonRedisClientFactory implements SingletonRedisClientFactory, Managed {
private static final RedisSingletonExtension redisSingletonExtension = RedisSingletonExtension.builder().build();
private final AtomicBoolean shutdownHookConfigured = new AtomicBoolean();
private LocalSingletonRedisClientFactory() {
try {
redisSingletonExtension.beforeAll(null);
redisSingletonExtension.beforeEach(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public RedisClient build(final ClientResources clientResources) {
if (shutdownHookConfigured.compareAndSet(false, true)) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
this.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
}
return RedisClient.create(clientResources, redisSingletonExtension.getRedisUri());
}
@Override
public void stop() throws Exception {
redisSingletonExtension.afterEach(null);
redisSingletonExtension.afterAll(null);
}
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.time.Duration;
@JsonTypeName("nowait")
public class NoWaitDogstatsdConfiguration extends DogstatsdConfiguration {
@Override
public Duration getShutdownWaitDuration() {
return Duration.ZERO;
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
@JsonTypeName("static")
public class StaticDynamicConfigurationManagerFactory implements DynamicConfigurationManagerFactory {
@JsonProperty
@NotEmpty
private String application;
@JsonProperty
@NotEmpty
private String environment;
@JsonProperty
@NotEmpty
private String configuration;
@JsonProperty
@NotBlank
private String staticConfig;
@Override
public <T> DynamicConfigurationManager<T> build(final Class<T> klazz,
final ScheduledExecutorService scheduledExecutorService, final AwsCredentialsProvider awsCredentialsProvider) {
return new StaticDynamicConfigurationManager<>(staticConfig, application, environment, configuration,
awsCredentialsProvider, klazz, scheduledExecutorService);
}
private static class StaticDynamicConfigurationManager<T> extends DynamicConfigurationManager<T> {
private final T configuration;
public StaticDynamicConfigurationManager(final String config, final String application, final String environment,
final String configurationName, final AwsCredentialsProvider awsCredentialsProvider,
final Class<T> configurationClass, final ScheduledExecutorService scheduledExecutorService) {
super(application, environment, configurationName, awsCredentialsProvider, configurationClass,
scheduledExecutorService);
try {
this.configuration = parseConfiguration(config, configurationClass).orElseThrow();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@Override
public T getConfiguration() {
return configuration;
}
@Override
public void start() {
// do nothing
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import org.whispersystems.textsecuregcm.s3.S3ObjectMonitor;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
@JsonTypeName("static")
public class StaticS3ObjectMonitorFactory implements S3ObjectMonitorFactory {
@JsonProperty
private byte[] object = new byte[0];
@Override
public S3ObjectMonitor build(final AwsCredentialsProvider awsCredentialsProvider,
final ScheduledExecutorService refreshExecutorService) {
return new StaticS3ObjectMonitor(object, awsCredentialsProvider);
}
private static class StaticS3ObjectMonitor extends S3ObjectMonitor {
private final byte[] object;
public StaticS3ObjectMonitor(final byte[] object, final AwsCredentialsProvider awsCredentialsProvider) {
super(awsCredentialsProvider, "local-test-region", "test-bucket", null, 0L, null, null);
this.object = object;
}
@Override
public synchronized void start(final Consumer<InputStream> changeListener) {
changeListener.accept(new ByteArrayInputStream(object));
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import org.whispersystems.textsecuregcm.captcha.Action;
import org.whispersystems.textsecuregcm.captcha.AssessmentResult;
import org.whispersystems.textsecuregcm.captcha.HCaptchaClient;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
@JsonTypeName("stub")
public class StubHCaptchaClientFactory implements HCaptchaClientFactory {
@Override
public HCaptchaClient build(final ScheduledExecutorService retryExecutor,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
return new StubHCaptchaClient(retryExecutor, new CircuitBreakerConfiguration(), dynamicConfigurationManager);
}
/**
* Accepts any token of the format "test.test.*.*"
*/
private static class StubHCaptchaClient extends HCaptchaClient {
public StubHCaptchaClient(final ScheduledExecutorService retryExecutor,
final CircuitBreakerConfiguration circuitBreakerConfiguration,
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
super(null, retryExecutor, circuitBreakerConfiguration, null, dynamicConfigurationManager);
}
@Override
public String scheme() {
return "test";
}
@Override
public Set<String> validSiteKeys(final Action action) {
return Set.of("test");
}
@Override
public AssessmentResult verify(final String siteKey, final Action action, final String token, final String ip)
throws IOException {
return AssessmentResult.alwaysValid();
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import java.math.BigDecimal;
import java.net.http.HttpClient;
import java.util.Collections;
import java.util.Map;
import org.whispersystems.textsecuregcm.currency.CoinMarketCapClient;
import org.whispersystems.textsecuregcm.currency.FixerClient;
@JsonTypeName("stub")
public class StubPaymentsServiceClientsFactory implements PaymentsServiceClientsFactory {
@Override
public FixerClient buildFixerClient(final HttpClient httpClient) {
return new StubFixerClient();
}
@Override
public CoinMarketCapClient buildCoinMarketCapClient(final HttpClient httpClient) {
return new StubCoinMarketCapClient();
}
/**
* Always returns an empty map of conversions
*/
private static class StubFixerClient extends FixerClient {
public StubFixerClient() {
super(null, null);
}
@Override
public Map<String, BigDecimal> getConversionsForBase(final String base) throws FixerException {
return Collections.emptyMap();
}
}
/**
* Always returns {@code 0} for spot price checks
*/
private static class StubCoinMarketCapClient extends CoinMarketCapClient {
public StubCoinMarketCapClient() {
super(null, null, null);
}
@Override
public BigDecimal getSpotPrice(final String currency, final String base) {
return BigDecimal.ZERO;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.api.core.ApiFutures;
import com.google.cloud.pubsub.v1.PublisherInterface;
import java.util.UUID;
@JsonTypeName("stub")
public class StubPubSubPublisherFactory implements PubSubPublisherFactory {
@Override
public PublisherInterface build() {
return message -> ApiFutures.immediateFuture(UUID.randomUUID().toString());
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import io.dropwizard.core.setup.Environment;
import java.io.IOException;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import javax.validation.constraints.NotNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.whispersystems.textsecuregcm.entities.RegistrationServiceSession;
import org.whispersystems.textsecuregcm.registration.ClientType;
import org.whispersystems.textsecuregcm.registration.MessageTransport;
import org.whispersystems.textsecuregcm.registration.RegistrationServiceClient;
@JsonTypeName("stub")
public class StubRegistrationServiceClientFactory implements RegistrationServiceClientFactory {
@JsonProperty
@NotNull
private String registrationCaCertificate;
@Override
public RegistrationServiceClient build(final Environment environment, final Executor callbackExecutor,
final ScheduledExecutorService identityRefreshExecutor) {
try {
return new StubRegistrationServiceClient(registrationCaCertificate);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static class StubRegistrationServiceClient extends RegistrationServiceClient {
private final static Map<String, RegistrationServiceSession> SESSIONS = new ConcurrentHashMap<>();
public StubRegistrationServiceClient(final String registrationCaCertificate) throws IOException {
super("example.com", 8080, null, registrationCaCertificate, null);
}
@Override
public CompletableFuture<RegistrationServiceSession> createRegistrationSession(
final Phonenumber.PhoneNumber phoneNumber, final boolean accountExistsWithPhoneNumber, final Duration timeout) {
final String e164 = PhoneNumberUtil.getInstance()
.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
final byte[] id = new byte[32];
new SecureRandom().nextBytes(id);
final RegistrationServiceSession session = new RegistrationServiceSession(id, e164, false, 0L, 0L, null,
Instant.now().plus(Duration.ofMinutes(10)).toEpochMilli());
SESSIONS.put(Base64.getEncoder().encodeToString(id), session);
return CompletableFuture.completedFuture(session);
}
@Override
public CompletableFuture<RegistrationServiceSession> sendVerificationCode(final byte[] sessionId,
final MessageTransport messageTransport, final ClientType clientType, final @Nullable String acceptLanguage,
final @Nullable String senderOverride, final Duration timeout) {
return CompletableFuture.completedFuture(SESSIONS.get(Base64.getEncoder().encodeToString(sessionId)));
}
@Override
public CompletableFuture<RegistrationServiceSession> checkVerificationCode(final byte[] sessionId,
final String verificationCode, final Duration timeout) {
final RegistrationServiceSession session = SESSIONS.get(Base64.getEncoder().encodeToString(sessionId));
final RegistrationServiceSession updatedSession = new RegistrationServiceSession(sessionId, session.number(),
true, 0L, 0L, 0L,
Instant.now().plus(Duration.ofMinutes(10)).toEpochMilli());
SESSIONS.put(Base64.getEncoder().encodeToString(sessionId), updatedSession);
return CompletableFuture.completedFuture(updatedSession);
}
@Override
public CompletableFuture<Optional<RegistrationServiceSession>> getSession(final byte[] sessionId,
final Duration timeout) {
return CompletableFuture.completedFuture(
Optional.ofNullable(SESSIONS.get(Base64.getEncoder().encodeToString(sessionId))));
}
}
}