mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 23:38:03 +01:00
Add tests for WhisperServerService#run
Additionally, `LocalWhisperServerService` may be used for integration testing.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user