mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 14:28:05 +01:00
Add API endpoints for waiting for newly-linked devices
This commit is contained in:
@@ -5,12 +5,14 @@
|
||||
package org.whispersystems.textsecuregcm.controllers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyByte;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
@@ -18,6 +20,7 @@ import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.amazonaws.util.Base64;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
@@ -25,6 +28,7 @@ import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -35,6 +39,7 @@ import java.util.stream.Stream;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.glassfish.jersey.server.ServerProperties;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -54,6 +59,7 @@ import org.whispersystems.textsecuregcm.auth.WebsocketRefreshApplicationEventLis
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceActivationRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceInfo;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
@@ -64,6 +70,7 @@ import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
@@ -79,7 +86,7 @@ import org.whispersystems.textsecuregcm.tests.util.MockRedisFuture;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
import org.whispersystems.textsecuregcm.util.TestRandomUtil;
|
||||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
import org.whispersystems.textsecuregcm.util.LinkDeviceToken;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class DeviceControllerTest {
|
||||
@@ -112,6 +119,7 @@ class DeviceControllerTest {
|
||||
.addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(AuthenticatedDevice.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addProvider(new WebsocketRefreshApplicationEventListener(accountsManager, clientPresenceManager))
|
||||
.addProvider(new DeviceLimitExceededExceptionMapper())
|
||||
@@ -122,6 +130,7 @@ class DeviceControllerTest {
|
||||
void setup() {
|
||||
when(rateLimiters.getAllocateDeviceLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVerifyDeviceLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getWaitForLinkedDeviceLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
when(primaryDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
|
||||
@@ -479,16 +488,17 @@ class DeviceControllerTest {
|
||||
final Optional<ApnRegistrationId> apnRegistrationId,
|
||||
final Optional<GcmRegistrationId> gcmRegistrationId) {
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(AuthHelper.VALID_ACCOUNT));
|
||||
when(accountsManager.generateLinkDeviceToken(any())).thenReturn("test");
|
||||
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(AuthHelper.VALID_ACCOUNT.getDevices()).thenReturn(List.of(existingDevice));
|
||||
|
||||
VerificationCode deviceCode = resources.getJerseyTest()
|
||||
final LinkDeviceToken deviceCode = resources.getJerseyTest()
|
||||
.target("/v1/devices/provisioning/code")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(VerificationCode.class);
|
||||
.get(LinkDeviceToken.class);
|
||||
|
||||
final ECSignedPreKey aciSignedPreKey;
|
||||
final ECSignedPreKey pniSignedPreKey;
|
||||
@@ -506,7 +516,7 @@ class DeviceControllerTest {
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(new IdentityKey(aciIdentityKeyPair.getPublicKey()));
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(new IdentityKey(pniIdentityKeyPair.getPublicKey()));
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest(deviceCode.verificationCode(),
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest(deviceCode.token(),
|
||||
new AccountAttributes(fetchesMessages, 1234, 5678, null, null, true, null),
|
||||
new DeviceActivationRequest(aciSignedPreKey, pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey, apnRegistrationId, gcmRegistrationId));
|
||||
|
||||
@@ -539,21 +549,22 @@ class DeviceControllerTest {
|
||||
final KEMSignedPreKey pniPqLastResortPreKey) {
|
||||
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(AuthHelper.VALID_ACCOUNT));
|
||||
when(accountsManager.generateLinkDeviceToken(any())).thenReturn("test");
|
||||
|
||||
final Device existingDevice = mock(Device.class);
|
||||
when(existingDevice.getId()).thenReturn(Device.PRIMARY_ID);
|
||||
when(AuthHelper.VALID_ACCOUNT.getDevices()).thenReturn(List.of(existingDevice));
|
||||
|
||||
VerificationCode deviceCode = resources.getJerseyTest()
|
||||
final LinkDeviceToken deviceCode = resources.getJerseyTest()
|
||||
.target("/v1/devices/provisioning/code")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(VerificationCode.class);
|
||||
.get(LinkDeviceToken.class);
|
||||
|
||||
when(account.getIdentityKey(IdentityType.ACI)).thenReturn(aciIdentityKey);
|
||||
when(account.getIdentityKey(IdentityType.PNI)).thenReturn(pniIdentityKey);
|
||||
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest(deviceCode.verificationCode(),
|
||||
final LinkDeviceRequest request = new LinkDeviceRequest(deviceCode.token(),
|
||||
new AccountAttributes(true, 1234, 5678, null, null, true, null),
|
||||
new DeviceActivationRequest(aciSignedPreKey, pniSignedPreKey, aciPqLastResortPreKey, pniPqLastResortPreKey, Optional.empty(), Optional.empty()));
|
||||
|
||||
@@ -931,4 +942,120 @@ class DeviceControllerTest {
|
||||
|
||||
verify(clientPublicKeysManager).setPublicKey(AuthHelper.VALID_ACCOUNT, AuthHelper.VALID_DEVICE.getId(), request.publicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForLinkedDevice() {
|
||||
final DeviceInfo deviceInfo = new DeviceInfo(Device.PRIMARY_ID,
|
||||
"Device name ciphertext".getBytes(StandardCharsets.UTF_8),
|
||||
System.currentTimeMillis(),
|
||||
System.currentTimeMillis());
|
||||
|
||||
final String tokenIdentifier = Base64.encodeAsString(new byte[32]);
|
||||
|
||||
when(accountsManager.waitForNewLinkedDevice(eq(tokenIdentifier), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(deviceInfo)));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
final DeviceInfo retrievedDeviceInfo = response.readEntity(DeviceInfo.class);
|
||||
assertEquals(deviceInfo.id(), retrievedDeviceInfo.id());
|
||||
assertArrayEquals(deviceInfo.name(), retrievedDeviceInfo.name());
|
||||
assertEquals(deviceInfo.created(), retrievedDeviceInfo.created());
|
||||
assertEquals(deviceInfo.lastSeen(), retrievedDeviceInfo.lastSeen());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForLinkedDeviceNoDeviceLinked() {
|
||||
final String tokenIdentifier = Base64.encodeAsString(new byte[32]);
|
||||
|
||||
when(accountsManager.waitForNewLinkedDevice(eq(tokenIdentifier), any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(204, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForLinkedDeviceBadTokenIdentifier() {
|
||||
final String tokenIdentifier = Base64.encodeAsString(new byte[32]);
|
||||
|
||||
when(accountsManager.waitForNewLinkedDevice(eq(tokenIdentifier), any()))
|
||||
.thenReturn(CompletableFuture.failedFuture(new IllegalArgumentException()));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void waitForLinkedDeviceBadTimeout(final int timeoutSeconds) {
|
||||
final String tokenIdentifier = Base64.encodeAsString(new byte[32]);
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.queryParam("timeout", timeoutSeconds)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Integer> waitForLinkedDeviceBadTimeout() {
|
||||
return List.of(0, -1, 3601);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void waitForLinkedDeviceBadTokenIdentifierLength(final String tokenIdentifier) {
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(400, response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> waitForLinkedDeviceBadTokenIdentifierLength() {
|
||||
return List.of(RandomStringUtils.randomAlphanumeric(DeviceController.MIN_TOKEN_IDENTIFIER_LENGTH - 1),
|
||||
RandomStringUtils.randomAlphanumeric(DeviceController.MAX_TOKEN_IDENTIFIER_LENGTH + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForLinkedDeviceRateLimited() throws RateLimitExceededException {
|
||||
final String tokenIdentifier = Base64.encodeAsString(new byte[32]);
|
||||
|
||||
doThrow(new RateLimitExceededException(null)).when(rateLimiter).validate(AuthHelper.VALID_UUID);
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/wait_for_linked_device/" + tokenIdentifier)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get()) {
|
||||
|
||||
assertEquals(429, response.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
@@ -142,6 +143,7 @@ public class AccountCreationDeletionIntegrationTest {
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
mock(FaultTolerantRedisClient.class),
|
||||
accountLockManager,
|
||||
keysManager,
|
||||
messagesManager,
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
@@ -137,6 +138,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
mock(FaultTolerantRedisClient.class),
|
||||
accountLockManager,
|
||||
keysManager,
|
||||
messagesManager,
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfigurati
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
@@ -124,6 +125,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
RedisClusterHelper.builder().stringCommands(commands).build(),
|
||||
mock(FaultTolerantRedisClient.class),
|
||||
accountLockManager,
|
||||
mock(KeysManager.class),
|
||||
mock(MessagesManager.class),
|
||||
|
||||
@@ -33,6 +33,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import io.lettuce.core.RedisException;
|
||||
import io.lettuce.core.api.async.RedisAsyncCommands;
|
||||
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import java.io.InputStream;
|
||||
@@ -78,6 +79,7 @@ import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecoveryException;
|
||||
@@ -88,6 +90,7 @@ import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.KeysHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.MockRedisFuture;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisServerHelper;
|
||||
import org.whispersystems.textsecuregcm.util.CompletableFutureTestUtil;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
import org.whispersystems.textsecuregcm.util.TestClock;
|
||||
@@ -119,8 +122,9 @@ class AccountsManagerTest {
|
||||
|
||||
private Map<String, UUID> phoneNumberIdentifiersByE164;
|
||||
|
||||
private RedisAdvancedClusterCommands<String, String> commands;
|
||||
private RedisAdvancedClusterAsyncCommands<String, String> asyncCommands;
|
||||
private RedisAsyncCommands<String, String> asyncCommands;
|
||||
private RedisAdvancedClusterCommands<String, String> clusterCommands;
|
||||
private RedisAdvancedClusterAsyncCommands<String, String> asyncClusterCommands;
|
||||
private AccountsManager accountsManager;
|
||||
private SecureValueRecovery2Client svr2Client;
|
||||
private DynamicConfiguration dynamicConfiguration;
|
||||
@@ -162,14 +166,18 @@ class AccountsManagerTest {
|
||||
}).when(clientPresenceExecutor).execute(any());
|
||||
|
||||
//noinspection unchecked
|
||||
commands = mock(RedisAdvancedClusterCommands.class);
|
||||
asyncCommands = mock(RedisAsyncCommands.class);
|
||||
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
//noinspection unchecked
|
||||
asyncCommands = mock(RedisAdvancedClusterAsyncCommands.class);
|
||||
when(asyncCommands.del(any(String[].class))).thenReturn(MockRedisFuture.completedFuture(0L));
|
||||
when(asyncCommands.get(any())).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
clusterCommands = mock(RedisAdvancedClusterCommands.class);
|
||||
|
||||
//noinspection unchecked
|
||||
asyncClusterCommands = mock(RedisAdvancedClusterAsyncCommands.class);
|
||||
when(asyncClusterCommands.del(any(String[].class))).thenReturn(MockRedisFuture.completedFuture(0L));
|
||||
when(asyncClusterCommands.get(any())).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncClusterCommands.set(any(), any(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
when(accounts.updateAsync(any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(accounts.updateTransactionallyAsync(any(), any())).thenReturn(CompletableFuture.completedFuture(null));
|
||||
@@ -230,15 +238,20 @@ class AccountsManagerTest {
|
||||
|
||||
CLOCK = TestClock.now();
|
||||
|
||||
final FaultTolerantRedisClusterClient redisCluster = RedisClusterHelper.builder()
|
||||
.stringCommands(commands)
|
||||
final FaultTolerantRedisClient pubSubClient = RedisServerHelper.builder()
|
||||
.stringAsyncCommands(asyncCommands)
|
||||
.build();
|
||||
|
||||
final FaultTolerantRedisClusterClient redisCluster = RedisClusterHelper.builder()
|
||||
.stringCommands(clusterCommands)
|
||||
.stringAsyncCommands(asyncClusterCommands)
|
||||
.build();
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
redisCluster,
|
||||
pubSubClient,
|
||||
accountLockManager,
|
||||
keysManager,
|
||||
messagesManager,
|
||||
@@ -285,8 +298,8 @@ class AccountsManagerTest {
|
||||
final UUID aci = UUID.randomUUID();
|
||||
final UUID pni = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("AccountMap::" + pni))).thenReturn(aci.toString());
|
||||
when(commands.get(eq("Account3::" + aci))).thenReturn(
|
||||
when(clusterCommands.get(eq("AccountMap::" + pni))).thenReturn(aci.toString());
|
||||
when(clusterCommands.get(eq("Account3::" + aci))).thenReturn(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"" + pni + "\"}");
|
||||
|
||||
assertTrue(accountsManager.getByServiceIdentifier(new AciServiceIdentifier(aci)).isPresent());
|
||||
@@ -300,11 +313,11 @@ class AccountsManagerTest {
|
||||
final UUID aci = UUID.randomUUID();
|
||||
final UUID pni = UUID.randomUUID();
|
||||
|
||||
when(asyncCommands.get(eq("AccountMap::" + pni))).thenReturn(MockRedisFuture.completedFuture(aci.toString()));
|
||||
when(asyncCommands.get(eq("Account3::" + aci))).thenReturn(MockRedisFuture.completedFuture(
|
||||
when(asyncClusterCommands.get(eq("AccountMap::" + pni))).thenReturn(MockRedisFuture.completedFuture(aci.toString()));
|
||||
when(asyncClusterCommands.get(eq("Account3::" + aci))).thenReturn(MockRedisFuture.completedFuture(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"" + pni + "\"}"));
|
||||
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
when(accounts.getByAccountIdentifierAsync(any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.empty()));
|
||||
@@ -323,7 +336,7 @@ class AccountsManagerTest {
|
||||
void testGetAccountByUuidInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenReturn(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}");
|
||||
|
||||
Optional<Account> account = accountsManager.getByAccountIdentifier(uuid);
|
||||
@@ -333,8 +346,8 @@ class AccountsManagerTest {
|
||||
assertEquals(account.get().getUuid(), uuid);
|
||||
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
|
||||
|
||||
verify(commands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
@@ -343,10 +356,10 @@ class AccountsManagerTest {
|
||||
void testGetAccountByUuidInCacheAsync() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
|
||||
when(asyncClusterCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}"));
|
||||
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
Optional<Account> account = accountsManager.getByAccountIdentifierAsync(uuid).join();
|
||||
|
||||
@@ -355,8 +368,8 @@ class AccountsManagerTest {
|
||||
assertEquals(account.get().getUuid(), uuid);
|
||||
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
|
||||
|
||||
verify(asyncCommands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
@@ -366,8 +379,8 @@ class AccountsManagerTest {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
UUID pni = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("AccountMap::" + pni))).thenReturn(uuid.toString());
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(
|
||||
when(clusterCommands.get(eq("AccountMap::" + pni))).thenReturn(uuid.toString());
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenReturn(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}");
|
||||
|
||||
Optional<Account> account = accountsManager.getByPhoneNumberIdentifier(pni);
|
||||
@@ -376,9 +389,9 @@ class AccountsManagerTest {
|
||||
assertEquals(account.get().getNumber(), "+14152222222");
|
||||
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + pni));
|
||||
verify(commands).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(clusterCommands).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
@@ -388,13 +401,13 @@ class AccountsManagerTest {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
UUID pni = UUID.randomUUID();
|
||||
|
||||
when(asyncCommands.get(eq("AccountMap::" + pni)))
|
||||
when(asyncClusterCommands.get(eq("AccountMap::" + pni)))
|
||||
.thenReturn(MockRedisFuture.completedFuture(uuid.toString()));
|
||||
|
||||
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
|
||||
when(asyncClusterCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
|
||||
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}"));
|
||||
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
Optional<Account> account = accountsManager.getByPhoneNumberIdentifierAsync(pni).join();
|
||||
|
||||
@@ -402,9 +415,9 @@ class AccountsManagerTest {
|
||||
assertEquals(account.get().getNumber(), "+14152222222");
|
||||
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
|
||||
|
||||
verify(asyncCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncCommands).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncClusterCommands).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
@@ -415,7 +428,7 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accounts.getByAccountIdentifier(eq(uuid))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByAccountIdentifier(uuid);
|
||||
@@ -423,10 +436,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands, times(1)).get(eq("Account3::" + uuid));
|
||||
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands, times(1)).get(eq("Account3::" + uuid));
|
||||
verify(clusterCommands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(clusterCommands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verify(accounts, times(1)).getByAccountIdentifier(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -438,8 +451,8 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(accounts.getByAccountIdentifierAsync(eq(uuid)))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
|
||||
|
||||
@@ -448,10 +461,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(asyncCommands).get(eq("Account3::" + uuid));
|
||||
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands).get(eq("Account3::" + uuid));
|
||||
verify(asyncClusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncClusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verify(accounts).getByAccountIdentifierAsync(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -464,7 +477,7 @@ class AccountsManagerTest {
|
||||
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("AccountMap::" + pni))).thenReturn(null);
|
||||
when(clusterCommands.get(eq("AccountMap::" + pni))).thenReturn(null);
|
||||
when(accounts.getByPhoneNumberIdentifier(pni)).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByPhoneNumberIdentifier(pni);
|
||||
@@ -472,10 +485,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + pni));
|
||||
verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(clusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(clusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verify(accounts).getByPhoneNumberIdentifier(pni);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -488,8 +501,8 @@ class AccountsManagerTest {
|
||||
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(asyncCommands.get(eq("AccountMap::" + pni))).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.get(eq("AccountMap::" + pni))).thenReturn(MockRedisFuture.completedFuture(null));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(accounts.getByPhoneNumberIdentifierAsync(pni))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
|
||||
|
||||
@@ -498,10 +511,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(asyncCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncClusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncClusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verify(accounts).getByPhoneNumberIdentifierAsync(pni);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -528,7 +541,7 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(accounts.getByAccountIdentifier(eq(uuid))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByAccountIdentifier(uuid);
|
||||
@@ -536,10 +549,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands, times(1)).get(eq("Account3::" + uuid));
|
||||
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands, times(1)).get(eq("Account3::" + uuid));
|
||||
verify(clusterCommands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(clusterCommands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verify(accounts, times(1)).getByAccountIdentifier(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -551,10 +564,10 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(asyncCommands.get(eq("Account3::" + uuid)))
|
||||
when(asyncClusterCommands.get(eq("Account3::" + uuid)))
|
||||
.thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost!")));
|
||||
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
when(accounts.getByAccountIdentifierAsync(eq(uuid)))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
|
||||
@@ -564,10 +577,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(asyncCommands).get(eq("Account3::" + uuid));
|
||||
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands).get(eq("Account3::" + uuid));
|
||||
verify(asyncClusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncClusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verify(accounts).getByAccountIdentifierAsync(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -580,7 +593,7 @@ class AccountsManagerTest {
|
||||
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("AccountMap::" + pni))).thenThrow(new RedisException("OH NO"));
|
||||
when(clusterCommands.get(eq("AccountMap::" + pni))).thenThrow(new RedisException("OH NO"));
|
||||
when(accounts.getByPhoneNumberIdentifier(pni)).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByPhoneNumberIdentifier(pni);
|
||||
@@ -588,10 +601,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + pni));
|
||||
verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
verify(clusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(clusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(clusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(clusterCommands);
|
||||
|
||||
verify(accounts).getByPhoneNumberIdentifier(pni);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -604,10 +617,10 @@ class AccountsManagerTest {
|
||||
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(asyncCommands.get(eq("AccountMap::" + pni)))
|
||||
when(asyncClusterCommands.get(eq("AccountMap::" + pni)))
|
||||
.thenReturn(MockRedisFuture.failedFuture(new RedisException("OH NO")));
|
||||
|
||||
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
when(asyncClusterCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
|
||||
|
||||
when(accounts.getByPhoneNumberIdentifierAsync(pni))
|
||||
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
|
||||
@@ -617,10 +630,10 @@ class AccountsManagerTest {
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(asyncCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncCommands);
|
||||
verify(asyncClusterCommands).get(eq("AccountMap::" + pni));
|
||||
verify(asyncClusterCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
|
||||
verify(asyncClusterCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(asyncClusterCommands);
|
||||
|
||||
verify(accounts).getByPhoneNumberIdentifierAsync(pni);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -632,7 +645,7 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
|
||||
when(accounts.getByAccountIdentifier(uuid)).thenReturn(
|
||||
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH])));
|
||||
@@ -658,7 +671,7 @@ class AccountsManagerTest {
|
||||
UUID pni = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(asyncClusterCommands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
|
||||
when(accounts.getByAccountIdentifierAsync(uuid)).thenReturn(CompletableFuture.completedFuture(
|
||||
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]))));
|
||||
@@ -684,7 +697,7 @@ class AccountsManagerTest {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(clusterCommands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accounts.getByAccountIdentifier(uuid)).thenReturn(Optional.empty())
|
||||
.thenReturn(Optional.of(account));
|
||||
when(accounts.create(any(), any())).thenThrow(ContestedOptimisticLockException.class);
|
||||
@@ -971,7 +984,7 @@ class AccountsManagerTest {
|
||||
pniSignedPreKey,
|
||||
aciPqLastResortPreKey,
|
||||
pniPqLastResortPreKey),
|
||||
accountsManager.generateDeviceLinkingToken(aci))
|
||||
accountsManager.generateLinkDeviceToken(aci))
|
||||
.join();
|
||||
|
||||
verify(keysManager).deleteSingleUsePreKeys(aci, nextDeviceId);
|
||||
@@ -1606,7 +1619,7 @@ class AccountsManagerTest {
|
||||
final UUID aci = UUID.randomUUID();
|
||||
|
||||
assertEquals(Optional.of(aci),
|
||||
accountsManager.checkDeviceLinkingToken(accountsManager.generateDeviceLinkingToken(aci)));
|
||||
accountsManager.checkDeviceLinkingToken(accountsManager.generateLinkDeviceToken(aci)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -1622,7 +1635,7 @@ class AccountsManagerTest {
|
||||
|
||||
return Stream.of(
|
||||
// Expired token
|
||||
Arguments.of(AccountsManager.generateDeviceLinkingToken(UUID.randomUUID(),
|
||||
Arguments.of(AccountsManager.generateLinkDeviceToken(UUID.randomUUID(),
|
||||
new SecretKeySpec(LINK_DEVICE_SECRET, AccountsManager.LINK_DEVICE_VERIFICATION_TOKEN_ALGORITHM),
|
||||
CLOCK),
|
||||
tokenTimestamp.plus(AccountsManager.LINK_DEVICE_TOKEN_EXPIRATION_DURATION).plusSeconds(1)),
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.Mockito;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
@@ -137,6 +138,7 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
mock(FaultTolerantRedisClient.class),
|
||||
accountLockManager,
|
||||
keysManager,
|
||||
messageManager,
|
||||
|
||||
@@ -13,6 +13,7 @@ import static org.mockito.Mockito.when;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Optional;
|
||||
@@ -29,9 +30,11 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.signal.libsignal.protocol.ecc.Curve;
|
||||
import org.signal.libsignal.protocol.ecc.ECKeyPair;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceInfo;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisServerExtension;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
@@ -59,6 +62,9 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
@RegisterExtension
|
||||
static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||
|
||||
@RegisterExtension
|
||||
static final RedisServerExtension PUBSUB_SERVER_EXTENSION = RedisServerExtension.builder().build();
|
||||
|
||||
private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault());
|
||||
|
||||
private ExecutorService accountLockExecutor;
|
||||
@@ -128,10 +134,16 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
when(registrationRecoveryPasswordsManager.removeForNumber(any()))
|
||||
.thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
PUBSUB_SERVER_EXTENSION.getRedisClient().useConnection(connection -> {
|
||||
connection.sync().flushall();
|
||||
connection.sync().configSet("notify-keyspace-events", "K$");
|
||||
});
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
PUBSUB_SERVER_EXTENSION.getRedisClient(),
|
||||
accountLockManager,
|
||||
keysManager,
|
||||
messagesManager,
|
||||
@@ -146,10 +158,14 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
CLOCK,
|
||||
"link-device-secret".getBytes(StandardCharsets.UTF_8),
|
||||
dynamicConfigurationManager);
|
||||
|
||||
accountsManager.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws InterruptedException {
|
||||
accountsManager.stop();
|
||||
|
||||
accountLockExecutor.shutdown();
|
||||
clientPresenceExecutor.shutdown();
|
||||
|
||||
@@ -187,7 +203,7 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
KeysHelper.signedECPreKey(2, pniKeyPair),
|
||||
KeysHelper.signedKEMPreKey(3, aciKeyPair),
|
||||
KeysHelper.signedKEMPreKey(4, pniKeyPair)),
|
||||
accountsManager.generateDeviceLinkingToken(account.getIdentifier(IdentityType.ACI)))
|
||||
accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI)))
|
||||
.join();
|
||||
|
||||
assertEquals(2, updatedAccountAndDevice.first().getDevices().size());
|
||||
@@ -216,7 +232,7 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
final Account account = AccountsHelper.createAccount(accountsManager, number);
|
||||
assertEquals(1, accountsManager.getByAccountIdentifier(account.getUuid()).orElseThrow().getDevices().size());
|
||||
|
||||
final String linkDeviceToken = accountsManager.generateDeviceLinkingToken(account.getIdentifier(IdentityType.ACI));
|
||||
final String linkDeviceToken = accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI));
|
||||
|
||||
final Pair<Account, Device> updatedAccountAndDevice =
|
||||
accountsManager.addDevice(account, new DeviceSpec(
|
||||
@@ -292,7 +308,7 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
KeysHelper.signedECPreKey(2, pniKeyPair),
|
||||
KeysHelper.signedKEMPreKey(3, aciKeyPair),
|
||||
KeysHelper.signedKEMPreKey(4, pniKeyPair)),
|
||||
accountsManager.generateDeviceLinkingToken(account.getIdentifier(IdentityType.ACI)))
|
||||
accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI)))
|
||||
.join();
|
||||
|
||||
final byte addedDeviceId = updatedAccountAndDevice.second().getId();
|
||||
@@ -346,7 +362,7 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
KeysHelper.signedECPreKey(2, pniKeyPair),
|
||||
KeysHelper.signedKEMPreKey(3, aciKeyPair),
|
||||
KeysHelper.signedKEMPreKey(4, pniKeyPair)),
|
||||
accountsManager.generateDeviceLinkingToken(account.getIdentifier(IdentityType.ACI)))
|
||||
accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI)))
|
||||
.join();
|
||||
|
||||
final byte addedDeviceId = updatedAccountAndDevice.second().getId();
|
||||
@@ -376,4 +392,110 @@ public class AddRemoveDeviceIntegrationTest {
|
||||
assertTrue(keysManager.getLastResort(retrievedAccount.getPhoneNumberIdentifier(), Device.PRIMARY_ID).join().isPresent());
|
||||
assertTrue(clientPublicKeysManager.findPublicKey(retrievedAccount.getUuid(), Device.PRIMARY_ID).join().isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForNewLinkedDevice() throws InterruptedException {
|
||||
final String number = PhoneNumberUtil.getInstance().format(
|
||||
PhoneNumberUtil.getInstance().getExampleNumber("US"),
|
||||
PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
final ECKeyPair aciKeyPair = Curve.generateKeyPair();
|
||||
final ECKeyPair pniKeyPair = Curve.generateKeyPair();
|
||||
|
||||
final Account account = AccountsHelper.createAccount(accountsManager, number);
|
||||
|
||||
final String linkDeviceToken = accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI));
|
||||
final String linkDeviceTokenIdentifier = AccountsManager.getLinkDeviceTokenIdentifier(linkDeviceToken);
|
||||
|
||||
final CompletableFuture<Optional<DeviceInfo>> displacedFuture =
|
||||
accountsManager.waitForNewLinkedDevice(linkDeviceTokenIdentifier, Duration.ofSeconds(5));
|
||||
|
||||
final CompletableFuture<Optional<DeviceInfo>> activeFuture =
|
||||
accountsManager.waitForNewLinkedDevice(linkDeviceTokenIdentifier, Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(Optional.empty(), displacedFuture.join());
|
||||
|
||||
final Pair<Account, Device> updatedAccountAndDevice =
|
||||
accountsManager.addDevice(account, new DeviceSpec(
|
||||
"device-name".getBytes(StandardCharsets.UTF_8),
|
||||
"password",
|
||||
"OWT",
|
||||
new Device.DeviceCapabilities(true, true, true, false),
|
||||
1,
|
||||
2,
|
||||
true,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
KeysHelper.signedECPreKey(1, aciKeyPair),
|
||||
KeysHelper.signedECPreKey(2, pniKeyPair),
|
||||
KeysHelper.signedKEMPreKey(3, aciKeyPair),
|
||||
KeysHelper.signedKEMPreKey(4, pniKeyPair)),
|
||||
linkDeviceToken)
|
||||
.join();
|
||||
|
||||
final Optional<DeviceInfo> maybeDeviceInfo = activeFuture.join();
|
||||
|
||||
assertTrue(maybeDeviceInfo.isPresent());
|
||||
final DeviceInfo deviceInfo = maybeDeviceInfo.get();
|
||||
|
||||
assertEquals(updatedAccountAndDevice.second().getId(), deviceInfo.id());
|
||||
assertEquals(updatedAccountAndDevice.second().getCreated(), deviceInfo.created());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForNewLinkedDeviceAlreadyAdded() throws InterruptedException {
|
||||
final String number = PhoneNumberUtil.getInstance().format(
|
||||
PhoneNumberUtil.getInstance().getExampleNumber("US"),
|
||||
PhoneNumberUtil.PhoneNumberFormat.E164);
|
||||
|
||||
final ECKeyPair aciKeyPair = Curve.generateKeyPair();
|
||||
final ECKeyPair pniKeyPair = Curve.generateKeyPair();
|
||||
|
||||
final Account account = AccountsHelper.createAccount(accountsManager, number);
|
||||
|
||||
final String linkDeviceToken = accountsManager.generateLinkDeviceToken(account.getIdentifier(IdentityType.ACI));
|
||||
final String linkDeviceTokenIdentifier = AccountsManager.getLinkDeviceTokenIdentifier(linkDeviceToken);
|
||||
|
||||
final Pair<Account, Device> updatedAccountAndDevice =
|
||||
accountsManager.addDevice(account, new DeviceSpec(
|
||||
"device-name".getBytes(StandardCharsets.UTF_8),
|
||||
"password",
|
||||
"OWT",
|
||||
new Device.DeviceCapabilities(true, true, true, false),
|
||||
1,
|
||||
2,
|
||||
true,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
KeysHelper.signedECPreKey(1, aciKeyPair),
|
||||
KeysHelper.signedECPreKey(2, pniKeyPair),
|
||||
KeysHelper.signedKEMPreKey(3, aciKeyPair),
|
||||
KeysHelper.signedKEMPreKey(4, pniKeyPair)),
|
||||
linkDeviceToken)
|
||||
.join();
|
||||
|
||||
final CompletableFuture<Optional<DeviceInfo>> linkedDeviceFuture =
|
||||
accountsManager.waitForNewLinkedDevice(linkDeviceTokenIdentifier, Duration.ofMinutes(1));
|
||||
|
||||
final Optional<DeviceInfo> maybeDeviceInfo = linkedDeviceFuture.join();
|
||||
|
||||
assertTrue(maybeDeviceInfo.isPresent());
|
||||
final DeviceInfo deviceInfo = maybeDeviceInfo.get();
|
||||
|
||||
assertEquals(updatedAccountAndDevice.second().getId(), deviceInfo.id());
|
||||
assertEquals(updatedAccountAndDevice.second().getCreated(), deviceInfo.created());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForNewLinkedDeviceTimeout() {
|
||||
final String linkDeviceToken = accountsManager.generateLinkDeviceToken(UUID.randomUUID());
|
||||
final String linkDeviceTokenIdentifier = AccountsManager.getLinkDeviceTokenIdentifier(linkDeviceToken);
|
||||
|
||||
final CompletableFuture<Optional<DeviceInfo>> linkedDeviceFuture =
|
||||
accountsManager.waitForNewLinkedDevice(linkDeviceTokenIdentifier, Duration.ofMillis(10));
|
||||
|
||||
final Optional<DeviceInfo> maybeDeviceInfo = linkedDeviceFuture.join();
|
||||
|
||||
assertTrue(maybeDeviceInfo.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.api.async.RedisAsyncCommands;
|
||||
import io.lettuce.core.api.reactive.RedisReactiveCommands;
|
||||
import io.lettuce.core.api.sync.RedisCommands;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClient;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RedisServerHelper {
|
||||
|
||||
public static RedisServerHelper.Builder builder() {
|
||||
return new RedisServerHelper.Builder();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static FaultTolerantRedisClient buildMockRedisClient(
|
||||
final RedisCommands<String, String> stringCommands,
|
||||
final RedisAsyncCommands<String, String> stringAsyncCommands,
|
||||
final RedisCommands<byte[], byte[]> binaryCommands,
|
||||
final RedisAsyncCommands<byte[], byte[]> binaryAsyncCommands,
|
||||
final RedisReactiveCommands<byte[], byte[]> binaryReactiveCommands) {
|
||||
final FaultTolerantRedisClient client = mock(FaultTolerantRedisClient.class);
|
||||
final StatefulRedisConnection<String, String> stringConnection = mock(StatefulRedisConnection.class);
|
||||
final StatefulRedisConnection<byte[], byte[]> binaryConnection = mock(StatefulRedisConnection.class);
|
||||
|
||||
when(stringConnection.sync()).thenReturn(stringCommands);
|
||||
when(stringConnection.async()).thenReturn(stringAsyncCommands);
|
||||
when(binaryConnection.sync()).thenReturn(binaryCommands);
|
||||
when(binaryConnection.async()).thenReturn(binaryAsyncCommands);
|
||||
when(binaryConnection.reactive()).thenReturn(binaryReactiveCommands);
|
||||
|
||||
when(client.withConnection(any(Function.class))).thenAnswer(invocation -> {
|
||||
return invocation.getArgument(0, Function.class).apply(stringConnection);
|
||||
});
|
||||
|
||||
doAnswer(invocation -> {
|
||||
invocation.getArgument(0, Consumer.class).accept(stringConnection);
|
||||
return null;
|
||||
}).when(client).useConnection(any(Consumer.class));
|
||||
|
||||
when(client.withBinaryConnection(any(Function.class))).thenAnswer(invocation -> {
|
||||
return invocation.getArgument(0, Function.class).apply(binaryConnection);
|
||||
});
|
||||
|
||||
doAnswer(invocation -> {
|
||||
invocation.getArgument(0, Consumer.class).accept(binaryConnection);
|
||||
return null;
|
||||
}).when(client).useBinaryConnection(any(Consumer.class));
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static class Builder {
|
||||
|
||||
private RedisCommands<String, String> stringCommands = mock(RedisCommands.class);
|
||||
private RedisAsyncCommands<String, String> stringAsyncCommands = mock(RedisAsyncCommands.class);
|
||||
|
||||
private RedisCommands<byte[], byte[]> binaryCommands = mock(RedisCommands.class);
|
||||
|
||||
private RedisAsyncCommands<byte[], byte[]> binaryAsyncCommands =
|
||||
mock(RedisAsyncCommands.class);
|
||||
|
||||
private RedisReactiveCommands<byte[], byte[]> binaryReactiveCommands =
|
||||
mock(RedisReactiveCommands.class);
|
||||
|
||||
private Builder() {
|
||||
|
||||
}
|
||||
|
||||
public RedisServerHelper.Builder stringCommands(final RedisCommands<String, String> stringCommands) {
|
||||
this.stringCommands = stringCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RedisServerHelper.Builder stringAsyncCommands(final RedisAsyncCommands<String, String> stringAsyncCommands) {
|
||||
this.stringAsyncCommands = stringAsyncCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RedisServerHelper.Builder binaryCommands(final RedisCommands<byte[], byte[]> binaryCommands) {
|
||||
this.binaryCommands = binaryCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RedisServerHelper.Builder binaryAsyncCommands(final RedisAsyncCommands<byte[], byte[]> binaryAsyncCommands) {
|
||||
this.binaryAsyncCommands = binaryAsyncCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RedisServerHelper.Builder binaryReactiveCommands(
|
||||
final RedisReactiveCommands<byte[], byte[]> binaryReactiveCommands) {
|
||||
this.binaryReactiveCommands = binaryReactiveCommands;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FaultTolerantRedisClient build() {
|
||||
return RedisServerHelper.buildMockRedisClient(stringCommands,
|
||||
stringAsyncCommands,
|
||||
binaryCommands,
|
||||
binaryAsyncCommands,
|
||||
binaryReactiveCommands);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user