mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 05:28:00 +01:00
Remove Accounts Postgres
This commit is contained in:
@@ -320,30 +320,19 @@ class DynamicConfigurationTest {
|
||||
final DynamicConfiguration emptyConfig =
|
||||
DynamicConfigurationManager.parseConfiguration(emptyConfigYaml).orElseThrow();
|
||||
|
||||
assertFalse(emptyConfig.getAccountsDynamoDbMigrationConfiguration().isBackgroundMigrationEnabled());
|
||||
assertFalse(emptyConfig.getAccountsDynamoDbMigrationConfiguration().isDeleteEnabled());
|
||||
assertFalse(emptyConfig.getAccountsDynamoDbMigrationConfiguration().isWriteEnabled());
|
||||
assertFalse(emptyConfig.getAccountsDynamoDbMigrationConfiguration().isReadEnabled());
|
||||
assertEquals(10, emptyConfig.getAccountsDynamoDbMigrationConfiguration().getDynamoCrawlerScanPageSize());
|
||||
}
|
||||
|
||||
{
|
||||
final String accountsDynamoDbMigrationConfig =
|
||||
"accountsDynamoDbMigration:\n"
|
||||
+ " backgroundMigrationEnabled: true\n"
|
||||
+ " backgroundMigrationExecutorThreads: 100\n"
|
||||
+ " deleteEnabled: true\n"
|
||||
+ " readEnabled: true\n"
|
||||
+ " writeEnabled: true";
|
||||
+ " dynamoCrawlerScanPageSize: 5000";
|
||||
|
||||
final DynamicAccountsDynamoDbMigrationConfiguration config =
|
||||
DynamicConfigurationManager.parseConfiguration(accountsDynamoDbMigrationConfig).orElseThrow()
|
||||
.getAccountsDynamoDbMigrationConfiguration();
|
||||
|
||||
assertTrue(config.isBackgroundMigrationEnabled());
|
||||
assertEquals(100, config.getBackgroundMigrationExecutorThreads());
|
||||
assertTrue(config.isDeleteEnabled());
|
||||
assertTrue(config.isWriteEnabled());
|
||||
assertTrue(config.isReadEnabled());
|
||||
assertEquals(5000, config.getDynamoCrawlerScanPageSize());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
||||
|
||||
public class AccountDatabaseCrawlerIntegrationTest extends AbstractRedisClusterTest {
|
||||
@@ -58,18 +55,15 @@ public class AccountDatabaseCrawlerIntegrationTest extends AbstractRedisClusterT
|
||||
when(firstAccount.getUuid()).thenReturn(FIRST_UUID);
|
||||
when(secondAccount.getUuid()).thenReturn(SECOND_UUID);
|
||||
|
||||
when(accountsManager.getAllFrom(CHUNK_SIZE)).thenReturn(new AccountCrawlChunk(List.of(firstAccount), FIRST_UUID));
|
||||
when(accountsManager.getAllFrom(any(UUID.class), eq(CHUNK_SIZE)))
|
||||
when(accountsManager.getAllFromDynamo(CHUNK_SIZE)).thenReturn(
|
||||
new AccountCrawlChunk(List.of(firstAccount), FIRST_UUID));
|
||||
when(accountsManager.getAllFromDynamo(any(UUID.class), eq(CHUNK_SIZE)))
|
||||
.thenReturn(new AccountCrawlChunk(List.of(secondAccount), SECOND_UUID))
|
||||
.thenReturn(new AccountCrawlChunk(Collections.emptyList(), null));
|
||||
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getAccountsDynamoDbMigrationConfiguration()).thenReturn(mock(DynamicAccountsDynamoDbMigrationConfiguration.class));
|
||||
|
||||
final AccountDatabaseCrawlerCache crawlerCache = new AccountDatabaseCrawlerCache(getRedisCluster());
|
||||
accountDatabaseCrawler = new AccountDatabaseCrawler(accountsManager, crawlerCache, List.of(listener), CHUNK_SIZE,
|
||||
CHUNK_INTERVAL_MS, mock(ExecutorService.class), dynamicConfigurationManager);
|
||||
CHUNK_INTERVAL_MS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,9 +72,9 @@ public class AccountDatabaseCrawlerIntegrationTest extends AbstractRedisClusterT
|
||||
assertFalse(accountDatabaseCrawler.doPeriodicWork());
|
||||
assertFalse(accountDatabaseCrawler.doPeriodicWork());
|
||||
|
||||
verify(accountsManager).getAllFrom(CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFrom(FIRST_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFrom(SECOND_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFromDynamo(CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFromDynamo(FIRST_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFromDynamo(SECOND_UUID, CHUNK_SIZE);
|
||||
|
||||
verify(listener).onCrawlStart();
|
||||
verify(listener).timeAndProcessCrawlChunk(Optional.empty(), List.of(firstAccount));
|
||||
@@ -98,9 +92,9 @@ public class AccountDatabaseCrawlerIntegrationTest extends AbstractRedisClusterT
|
||||
assertFalse(accountDatabaseCrawler.doPeriodicWork());
|
||||
assertFalse(accountDatabaseCrawler.doPeriodicWork());
|
||||
|
||||
verify(accountsManager, times(2)).getAllFrom(CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFrom(FIRST_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFrom(SECOND_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager, times(2)).getAllFromDynamo(CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFromDynamo(FIRST_UUID, CHUNK_SIZE);
|
||||
verify(accountsManager).getAllFromDynamo(SECOND_UUID, CHUNK_SIZE);
|
||||
|
||||
verify(listener, times(2)).onCrawlStart();
|
||||
verify(listener, times(2)).timeAndProcessCrawlChunk(Optional.empty(), List.of(firstAccount));
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit5.EmbeddedPostgresExtension;
|
||||
import com.opentable.db.postgres.junit5.PreparedDbExtension;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex;
|
||||
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
||||
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
||||
import software.amazon.awssdk.services.dynamodb.model.Projection;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ProjectionType;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
class AccountsDynamoDbMigrationCrawlerIntegrationTest {
|
||||
|
||||
private static final int CHUNK_SIZE = 20;
|
||||
private static final long CHUNK_INTERVAL_MS = 0;
|
||||
|
||||
private static final String ACCOUNTS_TABLE_NAME = "accounts_test";
|
||||
private static final String KEYS_TABLE_NAME = "keys_test";
|
||||
private static final String MIGRATION_DELETED_ACCOUNTS_TABLE_NAME = "migration_deleted_accounts_test";
|
||||
private static final String MIGRATION_RETRY_ACCOUNTS_TABLE_NAME = "migration_retry_accounts_test";
|
||||
private static final String NUMBERS_TABLE_NAME = "numbers_test";
|
||||
private static final String VERIFICATION_CODE_TABLE_NAME = "verification_code_test";
|
||||
|
||||
@RegisterExtension
|
||||
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
|
||||
|
||||
@RegisterExtension
|
||||
static final DynamoDbExtension KEYS_DYNAMODB_EXTENSION = DynamoDbExtension.builder()
|
||||
.tableName(KEYS_TABLE_NAME)
|
||||
.hashKey("U")
|
||||
.rangeKey("DK")
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName("U")
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName("DK")
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@RegisterExtension
|
||||
static final DynamoDbExtension VERIFICATION_CODE_DYNAMODB_EXTENSION = DynamoDbExtension.builder()
|
||||
.tableName(VERIFICATION_CODE_TABLE_NAME)
|
||||
.hashKey("P")
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName("P")
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@RegisterExtension
|
||||
static PreparedDbExtension db = EmbeddedPostgresExtension
|
||||
.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension ACCOUNTS_DYNAMODB_EXTENSION = DynamoDbExtension.builder()
|
||||
.tableName(ACCOUNTS_TABLE_NAME)
|
||||
.hashKey("U")
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName("U")
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test";
|
||||
|
||||
@RegisterExtension
|
||||
static final DynamoDbExtension DELETED_ACCOUNTS_DYNAMODB_EXTENSION = DynamoDbExtension.builder()
|
||||
.tableName("deleted_accounts_test")
|
||||
.hashKey(DeletedAccounts.KEY_ACCOUNT_E164)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.KEY_ACCOUNT_E164)
|
||||
.attributeType(ScalarAttributeType.S).build())
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.ATTR_NEEDS_CDS_RECONCILIATION)
|
||||
.attributeType(ScalarAttributeType.N)
|
||||
.build())
|
||||
.globalSecondaryIndex(GlobalSecondaryIndex.builder()
|
||||
.indexName(NEEDS_RECONCILIATION_INDEX_NAME)
|
||||
.keySchema(
|
||||
KeySchemaElement.builder().attributeName(DeletedAccounts.KEY_ACCOUNT_E164).keyType(KeyType.HASH).build(),
|
||||
KeySchemaElement.builder().attributeName(DeletedAccounts.ATTR_NEEDS_CDS_RECONCILIATION)
|
||||
.keyType(KeyType.RANGE).build())
|
||||
.projection(Projection.builder().projectionType(ProjectionType.INCLUDE)
|
||||
.nonKeyAttributes(DeletedAccounts.ATTR_ACCOUNT_UUID).build())
|
||||
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build())
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION = DynamoDbExtension.builder()
|
||||
.tableName("deleted_accounts_lock_test")
|
||||
.hashKey(DeletedAccounts.KEY_ACCOUNT_E164)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.KEY_ACCOUNT_E164)
|
||||
.attributeType(ScalarAttributeType.S).build())
|
||||
.build();
|
||||
|
||||
private DynamicAccountsDynamoDbMigrationConfiguration accountMigrationConfiguration;
|
||||
|
||||
private AccountsManager accountsManager;
|
||||
private AccountDatabaseCrawler accountDatabaseCrawler;
|
||||
private Accounts accounts;
|
||||
private AccountsDynamoDb accountsDynamoDb;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
|
||||
createAdditionalDynamoDbTables();
|
||||
|
||||
final DeletedAccounts deletedAccounts = new DeletedAccounts(DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient(),
|
||||
DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getTableName(),
|
||||
NEEDS_RECONCILIATION_INDEX_NAME);
|
||||
|
||||
final DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
|
||||
DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getLegacyDynamoClient(),
|
||||
DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getTableName());
|
||||
|
||||
MigrationDeletedAccounts migrationDeletedAccounts = new MigrationDeletedAccounts(
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient(), MIGRATION_DELETED_ACCOUNTS_TABLE_NAME);
|
||||
|
||||
MigrationRetryAccounts migrationRetryAccounts = new MigrationRetryAccounts(
|
||||
(ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient()),
|
||||
MIGRATION_RETRY_ACCOUNTS_TABLE_NAME);
|
||||
|
||||
accountsDynamoDb = new AccountsDynamoDb(
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient(),
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbAsyncClient(),
|
||||
new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>()),
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getTableName(),
|
||||
NUMBERS_TABLE_NAME,
|
||||
migrationDeletedAccounts,
|
||||
migrationRetryAccounts);
|
||||
|
||||
final KeysDynamoDb keysDynamoDb = new KeysDynamoDb(KEYS_DYNAMODB_EXTENSION.getDynamoDbClient(), KEYS_TABLE_NAME);
|
||||
|
||||
accounts = new Accounts(new FaultTolerantDatabase("accountsTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration()));
|
||||
|
||||
final DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
|
||||
|
||||
final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
when(rateLimiters.getVerifyLimiter()).thenReturn(mock(RateLimiter.class));
|
||||
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
accountMigrationConfiguration = new DynamicAccountsDynamoDbMigrationConfiguration();
|
||||
accountMigrationConfiguration.setBackgroundMigrationEnabled(true);
|
||||
accountMigrationConfiguration.setLogMismatches(true);
|
||||
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
when(dynamicConfiguration.getAccountsDynamoDbMigrationConfiguration()).thenReturn(accountMigrationConfiguration);
|
||||
|
||||
final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
|
||||
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq("accountsDynamoDbMigration"))).thenReturn(true);
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
accountsDynamoDb,
|
||||
REDIS_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
deletedAccountsManager,
|
||||
directoryQueue,
|
||||
keysDynamoDb,
|
||||
mock(MessagesManager.class),
|
||||
mock(MigrationMismatchedAccounts.class),
|
||||
mock(UsernamesManager.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureBackupClient.class),
|
||||
experimentEnrollmentManager,
|
||||
dynamicConfigurationManager);
|
||||
|
||||
final AccountsDynamoDbMigrator dynamoDbMigrator = new AccountsDynamoDbMigrator(accountsDynamoDb,
|
||||
dynamicConfigurationManager);
|
||||
final PushFeedbackProcessor pushFeedbackProcessor = new PushFeedbackProcessor(accountsManager);
|
||||
|
||||
final AccountDatabaseCrawlerCache crawlerCache = new AccountDatabaseCrawlerCache(
|
||||
REDIS_CLUSTER_EXTENSION.getRedisCluster());
|
||||
|
||||
// Using a synchronous service doesn’t meaningfully impact the test
|
||||
final ExecutorService chunkPreReadExecutorService = new SynchronousExecutorService();
|
||||
|
||||
accountDatabaseCrawler = new AccountDatabaseCrawler(accountsManager, crawlerCache,
|
||||
List.of(dynamoDbMigrator, pushFeedbackProcessor), CHUNK_SIZE,
|
||||
CHUNK_INTERVAL_MS, chunkPreReadExecutorService, dynamicConfigurationManager);
|
||||
}
|
||||
|
||||
void createAdditionalDynamoDbTables() {
|
||||
CreateTableRequest createNumbersTableRequest = CreateTableRequest.builder()
|
||||
.tableName(NUMBERS_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName("P")
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName("P")
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient().createTable(createNumbersTableRequest);
|
||||
|
||||
final CreateTableRequest createMigrationDeletedAccountsTableRequest = CreateTableRequest.builder()
|
||||
.tableName(MIGRATION_DELETED_ACCOUNTS_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName("U")
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName("U")
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient().createTable(createMigrationDeletedAccountsTableRequest);
|
||||
|
||||
final CreateTableRequest createMigrationRetryAccountsTableRequest = CreateTableRequest.builder()
|
||||
.tableName(MIGRATION_RETRY_ACCOUNTS_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName("U")
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName("U")
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient().createTable(createMigrationRetryAccountsTableRequest);
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,6 @@ import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.jdbi.v3.core.transaction.TransactionException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
@@ -36,21 +32,15 @@ import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
|
||||
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
|
||||
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
||||
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
|
||||
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.TransactionConflictException;
|
||||
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
|
||||
@@ -59,8 +49,6 @@ class AccountsDynamoDbTest {
|
||||
|
||||
private static final String ACCOUNTS_TABLE_NAME = "accounts_test";
|
||||
private static final String NUMBERS_TABLE_NAME = "numbers_test";
|
||||
private static final String MIGRATION_DELETED_ACCOUNTS_TABLE_NAME = "migration_deleted_accounts_test";
|
||||
private static final String MIGRATION_RETRY_ACCOUNTS_TABLE_NAME = "migration_retry_accounts_test";
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder()
|
||||
@@ -91,50 +79,11 @@ class AccountsDynamoDbTest {
|
||||
|
||||
dynamoDbExtension.getDynamoDbClient().createTable(createNumbersTableRequest);
|
||||
|
||||
final CreateTableRequest createMigrationDeletedAccountsTableRequest = CreateTableRequest.builder()
|
||||
.tableName(MIGRATION_DELETED_ACCOUNTS_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName(MigrationDeletedAccounts.KEY_UUID)
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName(MigrationDeletedAccounts.KEY_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
dynamoDbExtension.getDynamoDbClient().createTable(createMigrationDeletedAccountsTableRequest);
|
||||
|
||||
MigrationDeletedAccounts migrationDeletedAccounts = new MigrationDeletedAccounts(
|
||||
dynamoDbExtension.getDynamoDbClient(), MIGRATION_DELETED_ACCOUNTS_TABLE_NAME);
|
||||
|
||||
final CreateTableRequest createMigrationRetryAccountsTableRequest = CreateTableRequest.builder()
|
||||
.tableName(MIGRATION_RETRY_ACCOUNTS_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName(MigrationRetryAccounts.KEY_UUID)
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName(MigrationRetryAccounts.KEY_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
dynamoDbExtension.getDynamoDbClient().createTable(createMigrationRetryAccountsTableRequest);
|
||||
|
||||
MigrationRetryAccounts migrationRetryAccounts = new MigrationRetryAccounts((dynamoDbExtension.getDynamoDbClient()),
|
||||
MIGRATION_RETRY_ACCOUNTS_TABLE_NAME);
|
||||
|
||||
this.accountsDynamoDb = new AccountsDynamoDb(
|
||||
dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getDynamoDbAsyncClient(),
|
||||
new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>()),
|
||||
dynamoDbExtension.getTableName(),
|
||||
NUMBERS_TABLE_NAME,
|
||||
migrationDeletedAccounts,
|
||||
migrationRetryAccounts);
|
||||
NUMBERS_TABLE_NAME
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -283,15 +232,13 @@ class AccountsDynamoDbTest {
|
||||
void testUpdateWithMockTransactionConflictException() {
|
||||
|
||||
final DynamoDbClient dynamoDbClient = mock(DynamoDbClient.class);
|
||||
accountsDynamoDb = new AccountsDynamoDb(dynamoDbClient, mock(DynamoDbAsyncClient.class),
|
||||
new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>()),
|
||||
dynamoDbExtension.getTableName(), NUMBERS_TABLE_NAME, mock(MigrationDeletedAccounts.class),
|
||||
mock(MigrationRetryAccounts.class));
|
||||
accountsDynamoDb = new AccountsDynamoDb(dynamoDbClient,
|
||||
dynamoDbExtension.getTableName(), NUMBERS_TABLE_NAME);
|
||||
|
||||
when(dynamoDbClient.updateItem(any(UpdateItemRequest.class)))
|
||||
.thenThrow(TransactionConflictException.class);
|
||||
|
||||
Device device = generateDevice (1 );
|
||||
Device device = generateDevice(1);
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device));
|
||||
|
||||
assertThatThrownBy(() -> accountsDynamoDb.update(account)).isInstanceOfAny(ContestedOptimisticLockException.class);
|
||||
@@ -376,33 +323,6 @@ class AccountsDynamoDbTest {
|
||||
verifyStoredState(recreatedAccount.getNumber(), recreatedAccount.getUuid(),
|
||||
accountsDynamoDb.get(recreatedAccount.getUuid()).get(), recreatedAccount);
|
||||
}
|
||||
|
||||
verifyRecentlyDeletedAccountsTableItemCount(1);
|
||||
|
||||
Map<String, AttributeValue> primaryKey = MigrationDeletedAccounts.primaryKey(deletedAccount.getUuid());
|
||||
assertThat(dynamoDbExtension.getDynamoDbClient().getItem(GetItemRequest.builder()
|
||||
.tableName(MIGRATION_DELETED_ACCOUNTS_TABLE_NAME)
|
||||
.key(Map.of(MigrationDeletedAccounts.KEY_UUID, primaryKey.get(MigrationDeletedAccounts.KEY_UUID)))
|
||||
.build()))
|
||||
.isNotNull();
|
||||
|
||||
accountsDynamoDb.deleteRecentlyDeletedUuids();
|
||||
|
||||
verifyRecentlyDeletedAccountsTableItemCount(0);
|
||||
}
|
||||
|
||||
private void verifyRecentlyDeletedAccountsTableItemCount(int expectedItemCount) {
|
||||
int totalItems = 0;
|
||||
|
||||
for (ScanResponse page : dynamoDbExtension.getDynamoDbClient().scanPaginator(ScanRequest.builder()
|
||||
.tableName(MIGRATION_DELETED_ACCOUNTS_TABLE_NAME)
|
||||
.build())) {
|
||||
for (Map<String, AttributeValue> item : page.items()) {
|
||||
totalItems++;
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(totalItems).isEqualTo(expectedItemCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -437,9 +357,8 @@ class AccountsDynamoDbTest {
|
||||
when(client.updateItem(any(UpdateItemRequest.class)))
|
||||
.thenThrow(RuntimeException.class);
|
||||
|
||||
AccountsDynamoDb accounts = new AccountsDynamoDb(client, mock(DynamoDbAsyncClient.class), mock(ThreadPoolExecutor.class), ACCOUNTS_TABLE_NAME, NUMBERS_TABLE_NAME, mock(
|
||||
MigrationDeletedAccounts.class), mock(MigrationRetryAccounts.class));
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID());
|
||||
AccountsDynamoDb accounts = new AccountsDynamoDb(client, ACCOUNTS_TABLE_NAME, NUMBERS_TABLE_NAME);
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID());
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
@@ -472,42 +391,6 @@ class AccountsDynamoDbTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMigrate() throws ExecutionException, InterruptedException {
|
||||
|
||||
Device device = generateDevice (1 );
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
Account account = generateAccount("+14151112222", firstUuid, Collections.singleton(device));
|
||||
|
||||
boolean migrated = accountsDynamoDb.migrate(account).get();
|
||||
|
||||
assertThat(migrated).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", account.getUuid(), account, true);
|
||||
|
||||
migrated = accountsDynamoDb.migrate(account).get();
|
||||
|
||||
assertThat(migrated).isFalse();
|
||||
|
||||
verifyStoredState("+14151112222", account.getUuid(), account, true);
|
||||
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
device = generateDevice(1);
|
||||
Account accountRemigrationWithDifferentUuid = generateAccount("+14151112222", secondUuid, Collections.singleton(device));
|
||||
|
||||
migrated = accountsDynamoDb.migrate(accountRemigrationWithDifferentUuid).get();
|
||||
|
||||
assertThat(migrated).isFalse();
|
||||
verifyStoredState("+14151112222", firstUuid, account, true);
|
||||
|
||||
account.setVersion(account.getVersion() + 1);
|
||||
|
||||
migrated = accountsDynamoDb.migrate(account).get();
|
||||
|
||||
assertThat(migrated).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCanonicallyDiscoverableSet() {
|
||||
Device device = generateDevice(1);
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.opentable.db.postgres.junit5.PreparedDbExtension;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
@@ -34,18 +33,14 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
@@ -76,8 +71,6 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private Accounts accounts;
|
||||
|
||||
private AccountsDynamoDb accountsDynamoDb;
|
||||
|
||||
private AccountsManager accountsManager;
|
||||
@@ -108,22 +101,9 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
|
||||
accountsDynamoDb = new AccountsDynamoDb(
|
||||
dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getDynamoDbAsyncClient(),
|
||||
new ThreadPoolExecutor(1, 1, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>()),
|
||||
dynamoDbExtension.getTableName(),
|
||||
NUMBERS_TABLE_NAME,
|
||||
mock(MigrationDeletedAccounts.class),
|
||||
mock(MigrationRetryAccounts.class));
|
||||
|
||||
{
|
||||
final CircuitBreakerConfiguration circuitBreakerConfiguration = new CircuitBreakerConfiguration();
|
||||
circuitBreakerConfiguration.setIgnoredExceptions(List.of("org.whispersystems.textsecuregcm.storage.ContestedOptimisticLockException"));
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("accountsTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
circuitBreakerConfiguration);
|
||||
|
||||
accounts = new Accounts(faultTolerantDatabase);
|
||||
}
|
||||
NUMBERS_TABLE_NAME
|
||||
);
|
||||
|
||||
{
|
||||
final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
@@ -131,17 +111,6 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
DynamicConfiguration dynamicConfiguration = new DynamicConfiguration();
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
|
||||
final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
|
||||
|
||||
final DynamicAccountsDynamoDbMigrationConfiguration config = dynamicConfiguration
|
||||
.getAccountsDynamoDbMigrationConfiguration();
|
||||
|
||||
config.setDeleteEnabled(true);
|
||||
config.setReadEnabled(true);
|
||||
config.setWriteEnabled(true);
|
||||
|
||||
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), anyString())).thenReturn(true);
|
||||
|
||||
commands = mock(RedisAdvancedClusterCommands.class);
|
||||
|
||||
final DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
|
||||
@@ -153,20 +122,17 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
}).when(deletedAccountsManager).lockAndTake(anyString(), any());
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
accountsDynamoDb,
|
||||
RedisClusterHelper.buildMockRedisCluster(commands),
|
||||
deletedAccountsManager,
|
||||
mock(DirectoryQueue.class),
|
||||
mock(KeysDynamoDb.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(MigrationMismatchedAccounts.class),
|
||||
mock(UsernamesManager.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureBackupClient.class),
|
||||
experimentEnrollmentManager,
|
||||
dynamicConfigurationManager);
|
||||
}
|
||||
}
|
||||
@@ -225,14 +191,12 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
).join();
|
||||
|
||||
final Account managerAccount = accountsManager.get(uuid).get();
|
||||
final Account dbAccount = accounts.get(uuid).get();
|
||||
final Account dynamoAccount = accountsDynamoDb.get(uuid).get();
|
||||
|
||||
final Account redisAccount = getLastAccountFromRedisMock(commands);
|
||||
|
||||
Stream.of(
|
||||
new Pair<>("manager", managerAccount),
|
||||
new Pair<>("db", dbAccount),
|
||||
new Pair<>("dynamo", dynamoAccount),
|
||||
new Pair<>("redis", redisAccount)
|
||||
).forEach(pair ->
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
class MigrationDeletedAccountsTest {
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder()
|
||||
.tableName("deleted_accounts_test")
|
||||
.hashKey(MigrationDeletedAccounts.KEY_UUID)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(MigrationDeletedAccounts.KEY_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
final MigrationDeletedAccounts migrationDeletedAccounts = new MigrationDeletedAccounts(dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getTableName());
|
||||
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
assertTrue(migrationDeletedAccounts.getRecentlyDeletedUuids().isEmpty());
|
||||
|
||||
migrationDeletedAccounts.put(firstUuid);
|
||||
migrationDeletedAccounts.put(secondUuid);
|
||||
|
||||
assertTrue(migrationDeletedAccounts.getRecentlyDeletedUuids().containsAll(List.of(firstUuid, secondUuid)));
|
||||
|
||||
migrationDeletedAccounts.delete(List.of(firstUuid, secondUuid));
|
||||
|
||||
assertTrue(migrationDeletedAccounts.getRecentlyDeletedUuids().isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
class MigrationMismatchAccountsTest {
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder()
|
||||
.tableName("account_migration_mismatches_test")
|
||||
.hashKey(MigrationRetryAccounts.KEY_UUID)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(MigrationRetryAccounts.KEY_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
final Clock clock = mock(Clock.class);
|
||||
when(clock.millis()).thenReturn(0L);
|
||||
|
||||
final MigrationMismatchedAccounts migrationMismatchedAccounts = new MigrationMismatchedAccounts(
|
||||
dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getTableName(), clock);
|
||||
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
assertTrue(migrationMismatchedAccounts.getUuids(10).isEmpty());
|
||||
|
||||
migrationMismatchedAccounts.put(firstUuid);
|
||||
migrationMismatchedAccounts.put(secondUuid);
|
||||
|
||||
assertTrue(migrationMismatchedAccounts.getUuids(10).isEmpty());
|
||||
|
||||
when(clock.millis()).thenReturn(MigrationMismatchedAccounts.MISMATCH_CHECK_DELAY_MILLIS);
|
||||
|
||||
assertTrue(migrationMismatchedAccounts.getUuids(10).containsAll(List.of(firstUuid, secondUuid)));
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
class MigrationRetryAccountsTest {
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder()
|
||||
.tableName("account_migration_errors_test")
|
||||
.hashKey(MigrationRetryAccounts.KEY_UUID)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(MigrationRetryAccounts.KEY_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
|
||||
final MigrationRetryAccounts migrationRetryAccounts = new MigrationRetryAccounts(dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getTableName());
|
||||
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
assertTrue(migrationRetryAccounts.getUuids(10).isEmpty());
|
||||
|
||||
migrationRetryAccounts.put(firstUuid);
|
||||
migrationRetryAccounts.put(secondUuid);
|
||||
|
||||
assertTrue(migrationRetryAccounts.getUuids(10).containsAll(List.of(firstUuid, secondUuid)));
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -25,10 +24,7 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountCrawlChunk;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
|
||||
@@ -36,7 +32,6 @@ import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
|
||||
class AccountDatabaseCrawlerTest {
|
||||
|
||||
@@ -55,22 +50,14 @@ class AccountDatabaseCrawlerTest {
|
||||
|
||||
private final ExecutorService chunkPreReadExecutorService = mock(ExecutorService.class);
|
||||
|
||||
private final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
|
||||
private final AccountDatabaseCrawler crawler = new AccountDatabaseCrawler(accounts, cache, Arrays.asList(listener),
|
||||
CHUNK_SIZE, CHUNK_INTERVAL_MS, chunkPreReadExecutorService, dynamicConfigurationManager);
|
||||
private DynamicAccountsDynamoDbMigrationConfiguration dynamicAccountsDynamoDbMigrationConfiguration;
|
||||
CHUNK_SIZE, CHUNK_INTERVAL_MS);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(account1.getUuid()).thenReturn(ACCOUNT1);
|
||||
when(account2.getUuid()).thenReturn(ACCOUNT2);
|
||||
|
||||
when(accounts.getAllFrom(anyInt())).thenReturn(new AccountCrawlChunk(Arrays.asList(account1, account2), ACCOUNT2));
|
||||
when(accounts.getAllFrom(eq(ACCOUNT1), anyInt())).thenReturn(
|
||||
new AccountCrawlChunk(Arrays.asList(account2), ACCOUNT2));
|
||||
when(accounts.getAllFrom(eq(ACCOUNT2), anyInt())).thenReturn(new AccountCrawlChunk(Collections.emptyList(), null));
|
||||
|
||||
when(accounts.getAllFromDynamo(anyInt())).thenReturn(
|
||||
new AccountCrawlChunk(Arrays.asList(account1, account2), ACCOUNT2));
|
||||
when(accounts.getAllFromDynamo(eq(ACCOUNT1), anyInt())).thenReturn(
|
||||
@@ -81,17 +68,10 @@ class AccountDatabaseCrawlerTest {
|
||||
when(cache.claimActiveWork(any(), anyLong())).thenReturn(true);
|
||||
when(cache.isAccelerated()).thenReturn(false);
|
||||
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
dynamicAccountsDynamoDbMigrationConfiguration = mock(DynamicAccountsDynamoDbMigrationConfiguration.class);
|
||||
when(dynamicConfiguration.getAccountsDynamoDbMigrationConfiguration()).thenReturn(
|
||||
dynamicAccountsDynamoDbMigrationConfiguration);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlStart(final boolean useDynamo) throws AccountDatabaseCrawlerRestartException {
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(useDynamo);
|
||||
@Test
|
||||
void testCrawlStart() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastUuid()).thenReturn(Optional.empty());
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.empty());
|
||||
|
||||
@@ -99,20 +79,15 @@ class AccountDatabaseCrawlerTest {
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(useDynamo ? 0 : 1)).getLastUuid();
|
||||
verify(cache, times(useDynamo ? 1 : 0)).getLastUuidDynamo();
|
||||
verify(cache, times(0)).getLastUuid();
|
||||
verify(cache, times(1)).getLastUuidDynamo();
|
||||
verify(listener, times(1)).onCrawlStart();
|
||||
if (useDynamo) {
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(0)).getAllFromDynamo(any(UUID.class), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(1)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(0)).getAllFrom(any(UUID.class), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(0)).getAllFromDynamo(any(UUID.class), eq(CHUNK_SIZE));
|
||||
verify(account1, times(0)).getUuid();
|
||||
verify(listener, times(1)).timeAndProcessCrawlChunk(eq(Optional.empty()), eq(Arrays.asList(account1, account2)));
|
||||
verify(cache, times(useDynamo ? 0 : 1)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(useDynamo ? 1 : 0)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(0)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
@@ -123,10 +98,8 @@ class AccountDatabaseCrawlerTest {
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlChunk(final boolean useDynamo) throws AccountDatabaseCrawlerRestartException {
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(useDynamo);
|
||||
@Test
|
||||
void testCrawlChunk() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1));
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.of(ACCOUNT1));
|
||||
|
||||
@@ -134,18 +107,13 @@ class AccountDatabaseCrawlerTest {
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(useDynamo ? 0: 1)).getLastUuid();
|
||||
verify(cache, times(useDynamo ? 1: 0)).getLastUuidDynamo();
|
||||
if (useDynamo) {
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(cache, times(0)).getLastUuid();
|
||||
verify(cache, times(1)).getLastUuidDynamo();
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(listener, times(1)).timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(useDynamo ? 0 : 1)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(useDynamo ? 1 : 0)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(0)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
@@ -157,46 +125,8 @@ class AccountDatabaseCrawlerTest {
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlChunk_useDynamoDedicatedMigrationCrawler(final boolean dedicatedMigrationCrawler) throws Exception {
|
||||
crawler.setDedicatedDynamoMigrationCrawler(dedicatedMigrationCrawler);
|
||||
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(true);
|
||||
when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1));
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.of(ACCOUNT1));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(dedicatedMigrationCrawler ? 1 : 0)).getLastUuid();
|
||||
verify(cache, times(dedicatedMigrationCrawler ? 0 : 1)).getLastUuidDynamo();
|
||||
if (dedicatedMigrationCrawler) {
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(listener, times(1)).timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(dedicatedMigrationCrawler ? 1 : 0)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(dedicatedMigrationCrawler ? 0 : 1)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyNoInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlChunkAccelerated(final boolean useDynamo) throws AccountDatabaseCrawlerRestartException {
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(useDynamo);
|
||||
@Test
|
||||
void testCrawlChunkAccelerated() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.isAccelerated()).thenReturn(true);
|
||||
when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1));
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.of(ACCOUNT1));
|
||||
@@ -205,22 +135,17 @@ class AccountDatabaseCrawlerTest {
|
||||
assertThat(accelerated).isTrue();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(useDynamo ? 0 : 1)).getLastUuid();
|
||||
verify(cache, times(useDynamo ? 1 : 0)).getLastUuidDynamo();
|
||||
if (useDynamo) {
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(cache, times(0)).getLastUuid();
|
||||
verify(cache, times(1)).getLastUuidDynamo();
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(listener, times(1)).timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(useDynamo ? 0 : 1)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(useDynamo ? 1 : 0)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(0)).setLastUuid(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).setLastUuidDynamo(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
verifyNoInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -228,36 +153,30 @@ class AccountDatabaseCrawlerTest {
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlChunkRestart(final boolean useDynamo) throws AccountDatabaseCrawlerRestartException {
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(useDynamo);
|
||||
@Test
|
||||
void testCrawlChunkRestart() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT1));
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.of(ACCOUNT1));
|
||||
doThrow(AccountDatabaseCrawlerRestartException.class).when(listener).timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
doThrow(AccountDatabaseCrawlerRestartException.class).when(listener)
|
||||
.timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(useDynamo ? 0 : 1)).getLastUuid();
|
||||
verify(cache, times(useDynamo ? 1 : 0)).getLastUuidDynamo();
|
||||
if (useDynamo) {
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(cache, times(0)).getLastUuid();
|
||||
verify(cache, times(1)).getLastUuidDynamo();
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(account2, times(0)).getNumber();
|
||||
verify(listener, times(1)).timeAndProcessCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(useDynamo ? 0 : 1)).setLastUuid(eq(Optional.empty()));
|
||||
verify(cache, times(useDynamo ? 1 : 0)).setLastUuidDynamo(eq(Optional.empty()));
|
||||
verify(cache, times(0)).setLastUuid(eq(Optional.empty()));
|
||||
verify(cache, times(1)).setLastUuidDynamo(eq(Optional.empty()));
|
||||
verify(cache, times(1)).setAccelerated(false);
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
verifyNoInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
@@ -265,10 +184,8 @@ class AccountDatabaseCrawlerTest {
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testCrawlEnd(final boolean useDynamo) {
|
||||
when(dynamicAccountsDynamoDbMigrationConfiguration.isDynamoCrawlerEnabled()).thenReturn(useDynamo);
|
||||
@Test
|
||||
void testCrawlEnd() {
|
||||
when(cache.getLastUuid()).thenReturn(Optional.of(ACCOUNT2));
|
||||
when(cache.getLastUuidDynamo()).thenReturn(Optional.of(ACCOUNT2));
|
||||
|
||||
@@ -276,26 +193,21 @@ class AccountDatabaseCrawlerTest {
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(useDynamo ? 0 : 1)).getLastUuid();
|
||||
verify(cache, times(useDynamo ? 1 : 0)).getLastUuidDynamo();
|
||||
if (useDynamo) {
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT2), eq(CHUNK_SIZE));
|
||||
} else {
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT2), eq(CHUNK_SIZE));
|
||||
}
|
||||
verify(cache, times(0)).getLastUuid();
|
||||
verify(cache, times(1)).getLastUuidDynamo();
|
||||
verify(accounts, times(0)).getAllFromDynamo(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFromDynamo(eq(ACCOUNT2), eq(CHUNK_SIZE));
|
||||
verify(account1, times(0)).getNumber();
|
||||
verify(account2, times(0)).getNumber();
|
||||
verify(listener, times(1)).onCrawlEnd(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(useDynamo ? 0 : 1)).setLastUuid(eq(Optional.empty()));
|
||||
verify(cache, times(useDynamo ? 1 : 0)).setLastUuidDynamo(eq(Optional.empty()));
|
||||
verify(cache, times(0)).setLastUuid(eq(Optional.empty()));
|
||||
verify(cache, times(1)).setLastUuidDynamo(eq(Optional.empty()));
|
||||
verify(cache, times(1)).setAccelerated(false);
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
verifyZeroInteractions(account2);
|
||||
verifyNoInteractions(account1);
|
||||
verifyNoInteractions(account2);
|
||||
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
|
||||
@@ -33,6 +33,7 @@ import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
@@ -40,16 +41,13 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicAccountsDynamoDbMigrationConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.experiment.ExperimentEnrollmentManager;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ContestedOptimisticLockException;
|
||||
@@ -59,7 +57,6 @@ import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.KeysDynamoDb;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.MigrationMismatchedAccounts;
|
||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
@@ -68,12 +65,10 @@ import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
|
||||
class AccountsManagerTest {
|
||||
|
||||
private Accounts accounts;
|
||||
private AccountsDynamoDb accountsDynamoDb;
|
||||
private DeletedAccountsManager deletedAccountsManager;
|
||||
private DirectoryQueue directoryQueue;
|
||||
private DynamicConfigurationManager dynamicConfigurationManager;
|
||||
private ExperimentEnrollmentManager experimentEnrollmentManager;
|
||||
private KeysDynamoDb keys;
|
||||
private MessagesManager messagesManager;
|
||||
private ProfilesManager profilesManager;
|
||||
@@ -91,12 +86,10 @@ class AccountsManagerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws InterruptedException {
|
||||
accounts = mock(Accounts.class);
|
||||
accountsDynamoDb = mock(AccountsDynamoDb.class);
|
||||
deletedAccountsManager = mock(DeletedAccountsManager.class);
|
||||
directoryQueue = mock(DirectoryQueue.class);
|
||||
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
|
||||
keys = mock(KeysDynamoDb.class);
|
||||
messagesManager = mock(MessagesManager.class);
|
||||
profilesManager = mock(ProfilesManager.class);
|
||||
@@ -114,30 +107,24 @@ class AccountsManagerTest {
|
||||
}).when(deletedAccountsManager).lockAndTake(anyString(), any());
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
accountsDynamoDb,
|
||||
RedisClusterHelper.buildMockRedisCluster(commands),
|
||||
deletedAccountsManager,
|
||||
directoryQueue,
|
||||
keys,
|
||||
messagesManager,
|
||||
mock(MigrationMismatchedAccounts.class),
|
||||
mock(UsernamesManager.class),
|
||||
profilesManager,
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
mock(SecureBackupClient.class),
|
||||
experimentEnrollmentManager,
|
||||
dynamicConfigurationManager);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByNumberInCache(final boolean dynamoEnabled) {
|
||||
@Test
|
||||
void testGetAccountByNumberInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
|
||||
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(uuid.toString());
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
|
||||
|
||||
@@ -150,31 +137,14 @@ class AccountsManagerTest {
|
||||
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
|
||||
verify(commands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verifyNoInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
private void enableDynamo(boolean dynamoEnabled) {
|
||||
final DynamicAccountsDynamoDbMigrationConfiguration config = dynamicConfigurationManager.getConfiguration()
|
||||
.getAccountsDynamoDbMigrationConfiguration();
|
||||
|
||||
config.setDeleteEnabled(dynamoEnabled);
|
||||
config.setReadEnabled(dynamoEnabled);
|
||||
config.setWriteEnabled(dynamoEnabled);
|
||||
|
||||
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), anyString()))
|
||||
.thenReturn(dynamoEnabled);
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByUuidInCache(boolean dynamoEnabled) {
|
||||
@Test
|
||||
void testGetAccountByUuidInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
|
||||
|
||||
Optional<Account> account = accountsManager.get(uuid);
|
||||
@@ -186,22 +156,19 @@ class AccountsManagerTest {
|
||||
|
||||
verify(commands, times(1)).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verifyNoInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByNumberNotInCache(boolean dynamoEnabled) {
|
||||
@Test
|
||||
void testGetAccountByNumberNotInCache() {
|
||||
final boolean dynamoEnabled = true;
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
|
||||
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(null);
|
||||
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
when(accountsDynamoDb.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.get("+14152222222");
|
||||
|
||||
@@ -213,24 +180,18 @@ class AccountsManagerTest {
|
||||
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verify(accountsDynamoDb, dynamoEnabled ? times(1) : never())
|
||||
.get(eq("+14152222222"));
|
||||
verify(accountsDynamoDb, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByUuidNotInCache(boolean dynamoEnabled) {
|
||||
@Test
|
||||
void testGetAccountByUuidNotInCache() {
|
||||
final boolean dynamoEnabled = true;
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accounts.get(eq(uuid))).thenReturn(Optional.of(account));
|
||||
when(accountsDynamoDb.get(eq(uuid))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.get(uuid);
|
||||
|
||||
@@ -242,25 +203,19 @@ class AccountsManagerTest {
|
||||
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verify(accountsDynamoDb, dynamoEnabled ? times(1) : never()).get(eq(uuid));
|
||||
verify(accountsDynamoDb, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByNumberBrokenCache(boolean dynamoEnabled) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
@Test
|
||||
void testGetAccountByNumberBrokenCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
when(commands.get(eq("AccountMap::+14152222222"))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
when(accountsDynamoDb.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.get("+14152222222");
|
||||
Optional<Account> retrieved = accountsManager.get("+14152222222");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
@@ -270,25 +225,20 @@ class AccountsManagerTest {
|
||||
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verify(accountsDynamoDb, dynamoEnabled ? times(1) : never()).get(eq("+14152222222"));
|
||||
verify(accountsDynamoDb, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testGetAccountByUuidBrokenCache(boolean dynamoEnabled) {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
@Test
|
||||
void testGetAccountByUuidBrokenCache() {
|
||||
final boolean dynamoEnabled = true;
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(accounts.get(eq(uuid))).thenReturn(Optional.of(account));
|
||||
when(accountsDynamoDb.get(eq(uuid))).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.get(uuid);
|
||||
Optional<Account> retrieved = accountsManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
@@ -298,26 +248,25 @@ class AccountsManagerTest {
|
||||
verify(commands, times(1)).set(eq("Account3::" + uuid), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verify(accountsDynamoDb, dynamoEnabled ? times(1) : never()).get(eq(uuid));
|
||||
verify(accountsDynamoDb, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void testUpdate_dynamoDbMigration(boolean dynamoEnabled) throws IOException {
|
||||
// TODO delete
|
||||
@Disabled("migration specific")
|
||||
@Test
|
||||
void testUpdate_dynamoDbMigration() throws IOException {
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(dynamoEnabled);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
// database fetches should always return new instances
|
||||
when(accounts.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accounts).update(any(Account.class));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(
|
||||
Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(
|
||||
Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accountsDynamoDb).update(any(Account.class));
|
||||
|
||||
Account updatedAccount = accountsManager.update(account, a -> a.setProfileName("name"));
|
||||
|
||||
@@ -325,17 +274,13 @@ class AccountsManagerTest {
|
||||
|
||||
assertNotSame(updatedAccount, account);
|
||||
|
||||
verify(accounts, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verify(accountsDynamoDb, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
|
||||
if (dynamoEnabled) {
|
||||
ArgumentCaptor<Account> argumentCaptor = ArgumentCaptor.forClass(Account.class);
|
||||
verify(accountsDynamoDb, times(1)).update(argumentCaptor.capture());
|
||||
assertEquals(uuid, argumentCaptor.getValue().getUuid());
|
||||
} else {
|
||||
verify(accountsDynamoDb, never()).update(any());
|
||||
}
|
||||
verify(accountsDynamoDb, dynamoEnabled ? times(1) : never()).get(uuid);
|
||||
ArgumentCaptor<Account> argumentCaptor = ArgumentCaptor.forClass(Account.class);
|
||||
verify(accountsDynamoDb, times(1)).update(argumentCaptor.capture());
|
||||
assertEquals(uuid, argumentCaptor.getValue().getUuid());
|
||||
verify(accountsDynamoDb, times(1)).get(uuid);
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
|
||||
ArgumentCaptor<String> redisSetArgumentCapture = ArgumentCaptor.forClass(String.class);
|
||||
@@ -347,25 +292,26 @@ class AccountsManagerTest {
|
||||
// uuid is @JsonIgnore, so we need to set it for compareAccounts to work
|
||||
accountCached.setUuid(uuid);
|
||||
|
||||
assertEquals(Optional.empty(), accountsManager.compareAccounts(Optional.of(updatedAccount), Optional.of(accountCached)));
|
||||
assertEquals(Optional.empty(),
|
||||
accountsManager.compareAccounts(Optional.of(updatedAccount), Optional.of(accountCached)));
|
||||
}
|
||||
|
||||
// TODO delete
|
||||
@Disabled("migration specific")
|
||||
@Test
|
||||
void testUpdate_dynamoMissing() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(true);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(Optional.empty());
|
||||
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accounts).update(any());
|
||||
doAnswer(ACCOUNT_UPDATE_ANSWER).when(accountsDynamoDb).update(any());
|
||||
|
||||
Account updatedAccount = accountsManager.update(account, a -> {});
|
||||
Account updatedAccount = accountsManager.update(account, a -> {
|
||||
});
|
||||
|
||||
verify(accounts, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verify(accountsDynamoDb, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
|
||||
verify(accountsDynamoDb, never()).update(account);
|
||||
verify(accountsDynamoDb, times(1)).get(uuid);
|
||||
@@ -376,19 +322,19 @@ class AccountsManagerTest {
|
||||
|
||||
@Test
|
||||
void testUpdate_optimisticLockingFailure() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(true);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
|
||||
when(accounts.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(
|
||||
Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
doThrow(ContestedOptimisticLockException.class)
|
||||
.doAnswer(ACCOUNT_UPDATE_ANSWER)
|
||||
.when(accounts).update(any());
|
||||
.when(accountsDynamoDb).update(any());
|
||||
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(
|
||||
Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
doThrow(ContestedOptimisticLockException.class)
|
||||
.doAnswer(ACCOUNT_UPDATE_ANSWER)
|
||||
.when(accountsDynamoDb).update(any());
|
||||
@@ -398,12 +344,7 @@ class AccountsManagerTest {
|
||||
assertEquals(1, account.getVersion());
|
||||
assertEquals("name", account.getProfileName());
|
||||
|
||||
verify(accounts, times(1)).get(uuid);
|
||||
verify(accounts, times(2)).update(any());
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
// dynamo has an extra get() because the account is fetched before every update
|
||||
verify(accountsDynamoDb, times(2)).get(uuid);
|
||||
verify(accountsDynamoDb, times(1)).get(uuid);
|
||||
verify(accountsDynamoDb, times(2)).update(any());
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
@@ -413,8 +354,6 @@ class AccountsManagerTest {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
enableDynamo(true);
|
||||
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(Optional.empty())
|
||||
.thenReturn(Optional.of(account));
|
||||
@@ -422,10 +361,7 @@ class AccountsManagerTest {
|
||||
|
||||
accountsManager.update(account, a -> {});
|
||||
|
||||
verify(accounts, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
|
||||
verify(accountsDynamoDb, times(1)).get(uuid);
|
||||
verify(accountsDynamoDb, times(1)).update(account);
|
||||
verifyNoMoreInteractions(accountsDynamoDb);
|
||||
}
|
||||
|
||||
@@ -436,7 +372,8 @@ class AccountsManagerTest {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
Account account = new Account("+14152222222", uuid, new HashSet<>(), new byte[16]);
|
||||
|
||||
when(accounts.get(uuid)).thenReturn(Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
when(accountsDynamoDb.get(uuid)).thenReturn(
|
||||
Optional.of(new Account("+14152222222", uuid, new HashSet<>(), new byte[16])));
|
||||
|
||||
assertTrue(account.getDevices().isEmpty());
|
||||
|
||||
@@ -463,6 +400,8 @@ class AccountsManagerTest {
|
||||
verify(unknownDeviceUpdater, never()).accept(any(Device.class));
|
||||
}
|
||||
|
||||
// TODO delete
|
||||
@Disabled("migration specific")
|
||||
@Test
|
||||
void testCompareAccounts() throws Exception {
|
||||
assertEquals(Optional.empty(), accountsManager.compareAccounts(Optional.empty(), Optional.empty()));
|
||||
@@ -538,13 +477,13 @@ class AccountsManagerTest {
|
||||
|
||||
@Test
|
||||
void testCreateFreshAccount() throws InterruptedException {
|
||||
when(accounts.create(any())).thenReturn(true);
|
||||
when(accountsDynamoDb.create(any())).thenReturn(true);
|
||||
|
||||
final String e164 = "+18005550123";
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, true, null);
|
||||
accountsManager.create(e164, "password", null, attributes);
|
||||
|
||||
verify(accounts).create(argThat(account -> e164.equals(account.getNumber())));
|
||||
verify(accountsDynamoDb).create(argThat(account -> e164.equals(account.getNumber())));
|
||||
verifyNoInteractions(keys);
|
||||
verifyNoInteractions(messagesManager);
|
||||
verifyNoInteractions(profilesManager);
|
||||
@@ -554,7 +493,7 @@ class AccountsManagerTest {
|
||||
void testReregisterAccount() throws InterruptedException {
|
||||
final UUID existingUuid = UUID.randomUUID();
|
||||
|
||||
when(accounts.create(any())).thenAnswer(invocation -> {
|
||||
when(accountsDynamoDb.create(any())).thenAnswer(invocation -> {
|
||||
invocation.getArgument(0, Account.class).setUuid(existingUuid);
|
||||
return false;
|
||||
});
|
||||
@@ -563,7 +502,8 @@ class AccountsManagerTest {
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, true, null);
|
||||
accountsManager.create(e164, "password", null, attributes);
|
||||
|
||||
verify(accounts).create(argThat(account -> e164.equals(account.getNumber()) && existingUuid.equals(account.getUuid())));
|
||||
verify(accountsDynamoDb).create(
|
||||
argThat(account -> e164.equals(account.getNumber()) && existingUuid.equals(account.getUuid())));
|
||||
verify(keys).delete(existingUuid);
|
||||
verify(messagesManager).clear(existingUuid);
|
||||
verify(profilesManager).deleteAll(existingUuid);
|
||||
@@ -579,13 +519,14 @@ class AccountsManagerTest {
|
||||
return null;
|
||||
}).when(deletedAccountsManager).lockAndTake(anyString(), any());
|
||||
|
||||
when(accounts.create(any())).thenReturn(true);
|
||||
when(accountsDynamoDb.create(any())).thenReturn(true);
|
||||
|
||||
final String e164 = "+18005550123";
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 0, null, null, true, null);
|
||||
accountsManager.create(e164, "password", null, attributes);
|
||||
|
||||
verify(accounts).create(argThat(account -> e164.equals(account.getNumber()) && recentlyDeletedUuid.equals(account.getUuid())));
|
||||
verify(accountsDynamoDb).create(
|
||||
argThat(account -> e164.equals(account.getNumber()) && recentlyDeletedUuid.equals(account.getUuid())));
|
||||
verifyNoInteractions(keys);
|
||||
verifyNoInteractions(messagesManager);
|
||||
verifyNoInteractions(profilesManager);
|
||||
@@ -634,6 +575,7 @@ class AccountsManagerTest {
|
||||
verify(directoryQueue, times(expectRefresh ? 1 : 0)).refreshAccount(updatedAccount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> testUpdateDirectoryQueue() {
|
||||
return Stream.of(
|
||||
Arguments.of(false, false, false),
|
||||
@@ -654,7 +596,7 @@ class AccountsManagerTest {
|
||||
accountsManager.updateDeviceLastSeen(account, device, updatedLastSeen);
|
||||
|
||||
assertEquals(expectUpdate ? updatedLastSeen : initialLastSeen, device.getLastSeen());
|
||||
verify(accounts, expectUpdate ? times(1) : never()).update(account);
|
||||
verify(accountsDynamoDb, expectUpdate ? times(1) : never()).update(account);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import com.fasterxml.uuid.UUIDComparator;
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.jdbi.v3.core.HandleConsumer;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.jdbi.v3.core.transaction.TransactionException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountCrawlChunk;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.mappers.AccountRowMapper;
|
||||
|
||||
public class AccountsTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private Accounts accounts;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("accountsTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration());
|
||||
|
||||
this.accounts = new Accounts(faultTolerantDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStore() throws SQLException, IOException {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device));
|
||||
|
||||
boolean freshUser = accounts.create(account);
|
||||
assertThat(freshUser).isTrue();
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account.getUuid(), account);
|
||||
|
||||
freshUser = accounts.create(account);
|
||||
assertThat(freshUser).isTrue();
|
||||
|
||||
statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account.getUuid(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreMulti() throws SQLException, IOException {
|
||||
Set<Device> devices = new HashSet<>();
|
||||
devices.add(generateDevice(1));
|
||||
devices.add(generateDevice(2));
|
||||
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), devices);
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account.getUuid(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieve() {
|
||||
Set<Device> devicesFirst = new HashSet<>();
|
||||
devicesFirst.add(generateDevice(1));
|
||||
devicesFirst.add(generateDevice(2));
|
||||
|
||||
UUID uuidFirst = UUID.randomUUID();
|
||||
Account accountFirst = generateAccount("+14151112222", uuidFirst, devicesFirst);
|
||||
|
||||
Set<Device> devicesSecond = new HashSet<>();
|
||||
devicesSecond.add(generateDevice(1));
|
||||
devicesSecond.add(generateDevice(2));
|
||||
|
||||
UUID uuidSecond = UUID.randomUUID();
|
||||
Account accountSecond = generateAccount("+14152221111", uuidSecond, devicesSecond);
|
||||
|
||||
accounts.create(accountFirst);
|
||||
accounts.create(accountSecond);
|
||||
|
||||
Optional<Account> retrievedFirst = accounts.get("+14151112222");
|
||||
Optional<Account> retrievedSecond = accounts.get("+14152221111");
|
||||
|
||||
assertThat(retrievedFirst.isPresent()).isTrue();
|
||||
assertThat(retrievedSecond.isPresent()).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", uuidFirst, retrievedFirst.get(), accountFirst);
|
||||
verifyStoredState("+14152221111", uuidSecond, retrievedSecond.get(), accountSecond);
|
||||
|
||||
retrievedFirst = accounts.get(uuidFirst);
|
||||
retrievedSecond = accounts.get(uuidSecond);
|
||||
|
||||
assertThat(retrievedFirst.isPresent()).isTrue();
|
||||
assertThat(retrievedSecond.isPresent()).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", uuidFirst, retrievedFirst.get(), accountFirst);
|
||||
verifyStoredState("+14152221111", uuidSecond, retrievedSecond.get(), accountSecond);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverwrite() throws Exception {
|
||||
Device device = generateDevice (1 );
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
Account account = generateAccount("+14151112222", firstUuid, Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account.getUuid(), account);
|
||||
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
device = generateDevice(1);
|
||||
account = generateAccount("+14151112222", secondUuid, Collections.singleton(device));
|
||||
|
||||
final boolean freshUser = accounts.create(account);
|
||||
assertThat(freshUser).isFalse();
|
||||
verifyStoredState(statement, "+14151112222", firstUuid, account);
|
||||
|
||||
device = generateDevice(1);
|
||||
Account invalidAccount = generateAccount("+14151113333", firstUuid, Collections.singleton(device));
|
||||
|
||||
assertThatThrownBy(() -> accounts.create(invalidAccount));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
device.setName("foobar");
|
||||
|
||||
accounts.update(account);
|
||||
|
||||
account.setProfileName("profileName");
|
||||
|
||||
accounts.update(account);
|
||||
|
||||
assertThat(account.getVersion()).isEqualTo(2);
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+14151112222");
|
||||
|
||||
assertThat(retrieved.isPresent()).isTrue();
|
||||
verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account);
|
||||
|
||||
retrieved = accounts.get(account.getUuid());
|
||||
|
||||
assertThat(retrieved.isPresent()).isTrue();
|
||||
verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveFrom() {
|
||||
List<Account> users = new ArrayList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
Account account = generateAccount("+1" + String.format("%03d", i), UUID.randomUUID());
|
||||
users.add(account);
|
||||
accounts.create(account);
|
||||
}
|
||||
|
||||
users.sort((account, t1) -> UUIDComparator.staticCompare(account.getUuid(), t1.getUuid()));
|
||||
|
||||
AccountCrawlChunk retrieved = accounts.getAllFrom(10);
|
||||
assertThat(retrieved.getAccounts().size()).isEqualTo(10);
|
||||
|
||||
for (int i=0;i<retrieved.getAccounts().size();i++) {
|
||||
verifyStoredState(users.get(i).getNumber(), users.get(i).getUuid(), retrieved.getAccounts().get(i), users.get(i));
|
||||
}
|
||||
|
||||
for (int j=0;j<9;j++) {
|
||||
retrieved = accounts.getAllFrom(retrieved.getLastUuid().orElseThrow(), 10);
|
||||
assertThat(retrieved.getAccounts().size()).isEqualTo(10);
|
||||
|
||||
for (int i=0;i<retrieved.getAccounts().size();i++) {
|
||||
verifyStoredState(users.get(10 + (j * 10) + i).getNumber(), users.get(10 + (j * 10) + i).getUuid(), retrieved.getAccounts().get(i), users.get(10 + (j * 10) + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
final Device deletedDevice = generateDevice (1);
|
||||
final Account deletedAccount = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(deletedDevice));
|
||||
final Device retainedDevice = generateDevice (1);
|
||||
final Account retainedAccount = generateAccount("+14151112345", UUID.randomUUID(), Collections.singleton(retainedDevice));
|
||||
|
||||
accounts.create(deletedAccount);
|
||||
accounts.create(retainedAccount);
|
||||
|
||||
assertThat(accounts.get(deletedAccount.getUuid())).isPresent();
|
||||
assertThat(accounts.get(retainedAccount.getUuid())).isPresent();
|
||||
|
||||
accounts.delete(deletedAccount.getUuid());
|
||||
|
||||
assertThat(accounts.get(deletedAccount.getUuid())).isNotPresent();
|
||||
|
||||
verifyStoredState(retainedAccount.getNumber(), retainedAccount.getUuid(), accounts.get(retainedAccount.getUuid()).get(), retainedAccount);
|
||||
|
||||
{
|
||||
final Account recreatedAccount = generateAccount(deletedAccount.getNumber(), UUID.randomUUID(),
|
||||
Collections.singleton(generateDevice(1)));
|
||||
|
||||
final boolean freshUser = accounts.create(recreatedAccount);
|
||||
|
||||
assertThat(freshUser).isTrue();
|
||||
|
||||
assertThat(accounts.get(recreatedAccount.getUuid())).isPresent();
|
||||
verifyStoredState(recreatedAccount.getNumber(), recreatedAccount.getUuid(),
|
||||
accounts.get(recreatedAccount.getUuid()).get(), recreatedAccount);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVacuum() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
accounts.vacuum();
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+14151112222");
|
||||
assertThat(retrieved.isPresent()).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", account.getUuid(), retrieved.get(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissing() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+11111111");
|
||||
assertThat(retrieved.isPresent()).isFalse();
|
||||
|
||||
retrieved = accounts.get(UUID.randomUUID());
|
||||
assertThat(retrieved.isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreaker() throws InterruptedException {
|
||||
Jdbi jdbi = mock(Jdbi.class);
|
||||
doThrow(new TransactionException("Database error!")).when(jdbi).useHandle(any(HandleConsumer.class));
|
||||
|
||||
CircuitBreakerConfiguration configuration = new CircuitBreakerConfiguration();
|
||||
configuration.setWaitDurationInOpenStateInSeconds(1);
|
||||
configuration.setRingBufferSizeInHalfOpenState(1);
|
||||
configuration.setRingBufferSizeInClosedState(2);
|
||||
configuration.setFailureRateThreshold(50);
|
||||
|
||||
Accounts accounts = new Accounts(new FaultTolerantDatabase("testAccountBreaker", jdbi, configuration));
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID());
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (CallNotPermittedException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
Thread.sleep(1100);
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Device generateDevice(long id) {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(),
|
||||
"testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt() , 0, new Device.DeviceCapabilities(random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(), random.nextBoolean(),
|
||||
random.nextBoolean(), random.nextBoolean(), random.nextBoolean()));
|
||||
}
|
||||
|
||||
private Account generateAccount(String number, UUID uuid) {
|
||||
Device device = generateDevice(1);
|
||||
return generateAccount(number, uuid, Collections.singleton(device));
|
||||
}
|
||||
|
||||
private Account generateAccount(String number, UUID uuid, Set<Device> devices) {
|
||||
byte[] unidentifiedAccessKey = new byte[16];
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
Arrays.fill(unidentifiedAccessKey, (byte)random.nextInt(255));
|
||||
|
||||
return new Account(number, uuid, devices, unidentifiedAccessKey);
|
||||
}
|
||||
|
||||
private void verifyStoredState(PreparedStatement statement, String number, UUID uuid, Account expecting)
|
||||
throws SQLException, IOException
|
||||
{
|
||||
statement.setString(1, number);
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
if (resultSet.next()) {
|
||||
String data = resultSet.getString("data");
|
||||
assertThat(data).isNotEmpty();
|
||||
|
||||
Account result = new AccountRowMapper().map(resultSet, null);
|
||||
verifyStoredState(number, uuid, result, expecting);
|
||||
} else {
|
||||
throw new AssertionError("No data");
|
||||
}
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
private void verifyStoredState(String number, UUID uuid, Account result, Account expecting) {
|
||||
assertThat(result.getNumber()).isEqualTo(number);
|
||||
assertThat(result.getLastSeen()).isEqualTo(expecting.getLastSeen());
|
||||
assertThat(result.getUuid()).isEqualTo(uuid);
|
||||
assertThat(result.getVersion()).isEqualTo(expecting.getVersion());
|
||||
assertThat(Arrays.equals(result.getUnidentifiedAccessKey().get(), expecting.getUnidentifiedAccessKey().get())).isTrue();
|
||||
|
||||
for (Device expectingDevice : expecting.getDevices()) {
|
||||
Device resultDevice = result.getDevice(expectingDevice.getId()).get();
|
||||
assertThat(resultDevice.getApnId()).isEqualTo(expectingDevice.getApnId());
|
||||
assertThat(resultDevice.getGcmId()).isEqualTo(expectingDevice.getGcmId());
|
||||
assertThat(resultDevice.getLastSeen()).isEqualTo(expectingDevice.getLastSeen());
|
||||
assertThat(resultDevice.getSignedPreKey().getPublicKey()).isEqualTo(expectingDevice.getSignedPreKey().getPublicKey());
|
||||
assertThat(resultDevice.getSignedPreKey().getKeyId()).isEqualTo(expectingDevice.getSignedPreKey().getKeyId());
|
||||
assertThat(resultDevice.getSignedPreKey().getSignature()).isEqualTo(expectingDevice.getSignedPreKey().getSignature());
|
||||
assertThat(resultDevice.getFetchesMessages()).isEqualTo(expectingDevice.getFetchesMessages());
|
||||
assertThat(resultDevice.getUserAgent()).isEqualTo(expectingDevice.getUserAgent());
|
||||
assertThat(resultDevice.getName()).isEqualTo(expectingDevice.getName());
|
||||
assertThat(resultDevice.getCreated()).isEqualTo(expectingDevice.getCreated());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user