From 48ebafa4e0a68362fecba03ecb0a15dce10d32db Mon Sep 17 00:00:00 2001 From: Jonathan Klabunde Tomer <125505367+jkt-signal@users.noreply.github.com> Date: Mon, 3 Apr 2023 13:08:43 -0700 Subject: [PATCH] DynamoDBExtension refactor and helpers for our schema (#1327) There's a lot of boilerplate involved in setting up a DynamoDBExtension, and some tests were creating several extensions rather than one with several tables, which is probably slower than it has to be. This change adds a new DynamoDbExtensionSchema class in which we can define the Dynamo schema for tests, and refactors DynamoDbExtension to make it easy to instantiate a single extension with all the tables one wants (and no more, both to minimize test startup time and to ensure we explicitly test our dependencies and lack thereof). Tests requiring a DynamoDbExtension with a table schema that's not part of the normal Signal schema can instantiate a DynamoDbExtension.RawSchema instead. Test timings are unaffected, at least on my machine. Before: ```[INFO] service ............................................ SUCCESS [01:18 min]``` After: ```[INFO] service ............................................ SUCCESS [01:18 min]``` Co-authored-by: Jonathan Klabunde Tomer --- .../storage/MessagesDynamoDb.java | 14 +- ...ntsManagerChangeNumberIntegrationTest.java | 130 ++----- ...ConcurrentModificationIntegrationTest.java | 71 +--- ...ccountsManagerUsernameIntegrationTest.java | 103 ++---- .../textsecuregcm/storage/AccountsTest.java | 122 ++----- .../storage/DeletedAccountsManagerTest.java | 66 +--- .../storage/DeletedAccountsTest.java | 52 +-- .../storage/DynamoDbExtension.java | 146 +++----- .../storage/DynamoDbExtensionSchema.java | 336 ++++++++++++++++++ .../storage/IssuedReceiptsManagerTest.java | 17 +- .../textsecuregcm/storage/KeysTest.java | 22 +- .../MessagePersisterIntegrationTest.java | 12 +- .../storage/MessagesDynamoDbTest.java | 10 +- .../storage/PhoneNumberIdentifiersTest.java | 37 +- .../textsecuregcm/storage/ProfilesTest.java | 25 +- .../storage/PushChallengeDynamoDbTest.java | 19 +- .../storage/RegistrationRecoveryTest.java | 25 +- .../storage/RemoteConfigsTest.java | 16 +- .../storage/ReportMessageDynamoDbTest.java | 19 +- ...rializedExpireableJsonDynamoStoreTest.java | 20 +- .../storage/SubscriptionManagerTest.java | 39 +- .../storage/VerificationCodeStoreTest.java | 17 +- .../storage/VerificationSessionsTest.java | 17 +- .../storage/RedeemedReceiptsManagerTest.java | 18 +- .../tests/util/MessagesDynamoDbExtension.java | 40 --- .../WebSocketConnectionIntegrationTest.java | 8 +- 26 files changed, 574 insertions(+), 827 deletions(-) create mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtensionSchema.java delete mode 100644 service/src/test/java/org/whispersystems/textsecuregcm/tests/util/MessagesDynamoDbExtension.java diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDb.java b/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDb.java index ea7740de2..946a17e18 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDb.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDb.java @@ -45,11 +45,17 @@ import software.amazon.awssdk.services.dynamodb.model.WriteRequest; public class MessagesDynamoDb extends AbstractDynamoDbStore { - private static final String KEY_PARTITION = "H"; - private static final String KEY_SORT = "S"; + @VisibleForTesting + static final String KEY_PARTITION = "H"; - private static final String LOCAL_INDEX_MESSAGE_UUID_NAME = "Message_UUID_Index"; - private static final String LOCAL_INDEX_MESSAGE_UUID_KEY_SORT = "U"; + @VisibleForTesting + static final String KEY_SORT = "S"; + + @VisibleForTesting + static final String LOCAL_INDEX_MESSAGE_UUID_NAME = "Message_UUID_Index"; + + @VisibleForTesting + static final String LOCAL_INDEX_MESSAGE_UUID_KEY_SORT = "U"; private static final String KEY_TTL = "E"; private static final String KEY_ENVELOPE_BYTES = "EB"; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java index e3ba36750..c81166f5c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerChangeNumberIntegrationTest.java @@ -34,78 +34,28 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; -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; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class AccountsManagerChangeNumberIntegrationTest { - private static final String ACCOUNTS_TABLE_NAME = "accounts_test"; private static final String NUMBERS_TABLE_NAME = "numbers_test"; private static final String PNI_ASSIGNMENT_TABLE_NAME = "pni_assignment_test"; private static final String USERNAMES_TABLE_NAME = "usernames_test"; - private static final String PNI_TABLE_NAME = "pni_test"; - private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test"; - private static final String DELETED_ACCOUNTS_LOCK_TABLE_NAME = "deleted_accounts_lock_test"; private static final int SCAN_PAGE_SIZE = 1; @RegisterExtension - static DynamoDbExtension ACCOUNTS_DYNAMO_EXTENSION = DynamoDbExtension.builder() - .tableName(ACCOUNTS_TABLE_NAME) - .hashKey(Accounts.KEY_ACCOUNT_UUID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Accounts.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + Tables.ACCOUNTS, + Tables.DELETED_ACCOUNTS, + Tables.DELETED_ACCOUNTS_LOCK, + Tables.NUMBERS, + Tables.PNI, + Tables.PNI_ASSIGNMENTS, + Tables.USERNAMES); @RegisterExtension - static DynamoDbExtension DELETED_ACCOUNTS_DYNAMO_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_DYNAMO_EXTENSION = DynamoDbExtension.builder() - .tableName(DELETED_ACCOUNTS_LOCK_TABLE_NAME) - .hashKey(DeletedAccounts.KEY_ACCOUNT_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(DeletedAccounts.KEY_ACCOUNT_E164) - .attributeType(ScalarAttributeType.S).build()) - .build(); - - @RegisterExtension - static DynamoDbExtension PNI_DYNAMO_EXTENSION = DynamoDbExtension.builder() - .tableName(PNI_TABLE_NAME) - .hashKey(PhoneNumberIdentifiers.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(PhoneNumberIdentifiers.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); - - @RegisterExtension - static RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); + static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); private ClientPresenceManager clientPresenceManager; private DeletedAccounts deletedAccounts; @@ -115,40 +65,6 @@ class AccountsManagerChangeNumberIntegrationTest { @BeforeEach void setup() throws InterruptedException { - { - CreateTableRequest createNumbersTableRequest = CreateTableRequest.builder() - .tableName(NUMBERS_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createNumbersTableRequest); - } - - { - CreateTableRequest createPhoneNumberIdentifierTableRequest = CreateTableRequest.builder() - .tableName(PNI_ASSIGNMENT_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest); - } - { @SuppressWarnings("unchecked") final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); @@ -157,21 +73,21 @@ class AccountsManagerChangeNumberIntegrationTest { when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); final Accounts accounts = new Accounts( - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbAsyncClient(), - ACCOUNTS_DYNAMO_EXTENSION.getTableName(), - NUMBERS_TABLE_NAME, - PNI_ASSIGNMENT_TABLE_NAME, - USERNAMES_TABLE_NAME, + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Tables.ACCOUNTS.tableName(), + Tables.NUMBERS.tableName(), + Tables.PNI_ASSIGNMENTS.tableName(), + Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE); - deletedAccounts = new DeletedAccounts(DELETED_ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), - DELETED_ACCOUNTS_DYNAMO_EXTENSION.getTableName(), - NEEDS_RECONCILIATION_INDEX_NAME); + deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.DELETED_ACCOUNTS.tableName(), + Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName()); final DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, - DELETED_ACCOUNTS_LOCK_DYNAMO_EXTENSION.getLegacyDynamoClient(), - DELETED_ACCOUNTS_LOCK_DYNAMO_EXTENSION.getTableName()); + DYNAMO_DB_EXTENSION.getLegacyDynamoClient(), + Tables.DELETED_ACCOUNTS_LOCK.tableName()); final SecureStorageClient secureStorageClient = mock(SecureStorageClient.class); when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null)); @@ -185,7 +101,7 @@ class AccountsManagerChangeNumberIntegrationTest { clientPresenceManager = mock(ClientPresenceManager.class); final PhoneNumberIdentifiers phoneNumberIdentifiers = - new PhoneNumberIdentifiers(PNI_DYNAMO_EXTENSION.getDynamoDbClient(), PNI_TABLE_NAME); + new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.PNI.tableName()); accountsManager = new AccountsManager( accounts, diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java index 7398385f2..320ad88c6 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerConcurrentModificationIntegrationTest.java @@ -48,34 +48,23 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.tests.util.DevicesHelper; import org.whispersystems.textsecuregcm.tests.util.JsonHelpers; import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper; import org.whispersystems.textsecuregcm.util.Pair; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; -import software.amazon.awssdk.services.dynamodb.model.KeyType; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; + class AccountsManagerConcurrentModificationIntegrationTest { - private static final String ACCOUNTS_TABLE_NAME = "accounts_test"; - private static final String NUMBERS_TABLE_NAME = "numbers_test"; - private static final String PNI_TABLE_NAME = "pni_test"; - private static final String USERNAMES_TABLE_NAME = "usernames_test"; - private static final int SCAN_PAGE_SIZE = 1; @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(ACCOUNTS_TABLE_NAME) - .hashKey(Accounts.KEY_ACCOUNT_UUID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Accounts.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + Tables.ACCOUNTS, + Tables.NUMBERS, + Tables.PNI_ASSIGNMENTS + ); private Accounts accounts; @@ -88,51 +77,17 @@ class AccountsManagerConcurrentModificationIntegrationTest { @BeforeEach void setup() throws InterruptedException { - { - CreateTableRequest createNumbersTableRequest = CreateTableRequest.builder() - .tableName(NUMBERS_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - dynamoDbExtension.getDynamoDbClient().createTable(createNumbersTableRequest); - } - - { - CreateTableRequest createPhoneNumberIdentifierTableRequest = CreateTableRequest.builder() - .tableName(PNI_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - dynamoDbExtension.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest); - } - @SuppressWarnings("unchecked") final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class); when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration()); accounts = new Accounts( - dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), - dynamoDbExtension.getTableName(), - NUMBERS_TABLE_NAME, - PNI_TABLE_NAME, - USERNAMES_TABLE_NAME, + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Tables.ACCOUNTS.tableName(), + Tables.NUMBERS.tableName(), + Tables.PNI_ASSIGNMENTS.tableName(), + Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE); { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java index c63c2c1a6..c00a94fec 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsManagerUsernameIntegrationTest.java @@ -45,23 +45,14 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.util.AttributeValues; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; -import software.amazon.awssdk.services.dynamodb.model.KeyType; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; class AccountsManagerUsernameIntegrationTest { - private static final String ACCOUNTS_TABLE_NAME = "accounts_test"; - private static final String NUMBERS_TABLE_NAME = "numbers_test"; - private static final String PNI_ASSIGNMENT_TABLE_NAME = "pni_assignment_test"; - private static final String USERNAMES_TABLE_NAME = "usernames_test"; - private static final String PNI_TABLE_NAME = "pni_test"; private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE"; private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc"; private static final int SCAN_PAGE_SIZE = 1; @@ -69,24 +60,12 @@ class AccountsManagerUsernameIntegrationTest { private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2); @RegisterExtension - static DynamoDbExtension ACCOUNTS_DYNAMO_EXTENSION = DynamoDbExtension.builder() - .tableName(ACCOUNTS_TABLE_NAME) - .hashKey(Accounts.KEY_ACCOUNT_UUID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Accounts.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); - - @RegisterExtension - static DynamoDbExtension PNI_DYNAMO_EXTENSION = DynamoDbExtension.builder() - .tableName(PNI_TABLE_NAME) - .hashKey(PhoneNumberIdentifiers.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(PhoneNumberIdentifiers.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + Tables.ACCOUNTS, + Tables.NUMBERS, + Tables.USERNAMES, + Tables.PNI, + Tables.PNI_ASSIGNMENTS); @RegisterExtension static RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); @@ -96,48 +75,6 @@ class AccountsManagerUsernameIntegrationTest { @BeforeEach void setup() throws InterruptedException { - CreateTableRequest createNumbersTableRequest = CreateTableRequest.builder() - .tableName(NUMBERS_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createNumbersTableRequest); - CreateTableRequest createUsernamesTableRequest = CreateTableRequest.builder() - .tableName(USERNAMES_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_USERNAME_HASH) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_USERNAME_HASH) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createUsernamesTableRequest); - CreateTableRequest createPhoneNumberIdentifierTableRequest = CreateTableRequest.builder() - .tableName(PNI_ASSIGNMENT_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest); buildAccountsManager(1, 2, 10); } @@ -150,12 +87,12 @@ class AccountsManagerUsernameIntegrationTest { when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); accounts = Mockito.spy(new Accounts( - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbAsyncClient(), - ACCOUNTS_DYNAMO_EXTENSION.getTableName(), - NUMBERS_TABLE_NAME, - PNI_ASSIGNMENT_TABLE_NAME, - USERNAMES_TABLE_NAME, + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Tables.ACCOUNTS.tableName(), + Tables.NUMBERS.tableName(), + Tables.PNI_ASSIGNMENTS.tableName(), + Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE)); final DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class); @@ -167,7 +104,7 @@ class AccountsManagerUsernameIntegrationTest { }).when(deletedAccountsManager).lockAndTake(any(), any()); final PhoneNumberIdentifiers phoneNumberIdentifiers = - new PhoneNumberIdentifiers(PNI_DYNAMO_EXTENSION.getDynamoDbClient(), PNI_TABLE_NAME); + new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.PNI.tableName()); final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class); when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME))) @@ -207,8 +144,8 @@ class AccountsManagerUsernameIntegrationTest { AttributeValues.fromLong(Instant.now().plus(Duration.ofMinutes(1)).getEpochSecond())); } i++; - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() - .tableName(USERNAMES_TABLE_NAME) + DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() + .tableName(Tables.USERNAMES.tableName()) .item(item) .build()); } @@ -222,8 +159,8 @@ class AccountsManagerUsernameIntegrationTest { new ArrayList<>()); ArrayList usernameHashes = new ArrayList<>(Arrays.asList(USERNAME_HASH_1, USERNAME_HASH_2)); for (byte[] hash : usernameHashes) { - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() - .tableName(USERNAMES_TABLE_NAME) + DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() + .tableName(Tables.USERNAMES.tableName()) .item(Map.of( Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()), Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(hash))) @@ -287,8 +224,8 @@ class AccountsManagerUsernameIntegrationTest { long past = Instant.now().minus(Duration.ofMinutes(1)).getEpochSecond(); // force expiration - ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().updateItem(UpdateItemRequest.builder() - .tableName(USERNAMES_TABLE_NAME) + DYNAMO_DB_EXTENSION.getDynamoDbClient().updateItem(UpdateItemRequest.builder() + .tableName(Tables.USERNAMES.tableName()) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .updateExpression("SET #ttl = :ttl") .expressionAttributeNames(Map.of("#ttl", Accounts.ATTR_TTL)) diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java index a8f51c5db..7ff4e13f3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/AccountsTest.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.tests.util.AccountsHelper; import org.whispersystems.textsecuregcm.tests.util.DevicesHelper; import org.whispersystems.textsecuregcm.util.AttributeValues; @@ -45,18 +46,13 @@ import org.whispersystems.textsecuregcm.util.SystemMapper; import org.whispersystems.textsecuregcm.util.TestClock; 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.Put; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.ReturnValuesOnConditionCheckFailure; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; import software.amazon.awssdk.services.dynamodb.model.TransactWriteItem; import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest; import software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException; @@ -65,10 +61,6 @@ import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; class AccountsTest { - private static final String ACCOUNTS_TABLE_NAME = "accounts_test"; - private static final String NUMBER_CONSTRAINT_TABLE_NAME = "numbers_test"; - private static final String PNI_CONSTRAINT_TABLE_NAME = "pni_test"; - private static final String USERNAME_CONSTRAINT_TABLE_NAME = "username_test"; private static final String BASE_64_URL_USERNAME_HASH_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE"; private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc"; private static final byte[] USERNAME_HASH_1 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_1); @@ -77,16 +69,12 @@ class AccountsTest { private static final int SCAN_PAGE_SIZE = 1; - @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(ACCOUNTS_TABLE_NAME) - .hashKey(Accounts.KEY_ACCOUNT_UUID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Accounts.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + Tables.ACCOUNTS, + Tables.NUMBERS, + Tables.PNI_ASSIGNMENTS, + Tables.USERNAMES); private final TestClock clock = TestClock.pinned(Instant.EPOCH); private DynamicConfigurationManager mockDynamicConfigManager; @@ -94,50 +82,6 @@ class AccountsTest { @BeforeEach void setupAccountsDao() { - CreateTableRequest createNumbersTableRequest = CreateTableRequest.builder() - .tableName(NUMBER_CONSTRAINT_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_ACCOUNT_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - dynamoDbExtension.getDynamoDbClient().createTable(createNumbersTableRequest); - - CreateTableRequest createPhoneNumberIdentifierTableRequest = CreateTableRequest.builder() - .tableName(PNI_CONSTRAINT_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_PNI_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - dynamoDbExtension.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest); - - CreateTableRequest createUsernamesTableRequest = CreateTableRequest.builder() - .tableName(USERNAME_CONSTRAINT_TABLE_NAME) - .keySchema(KeySchemaElement.builder() - .attributeName(Accounts.ATTR_USERNAME_HASH) - .keyType(KeyType.HASH) - .build()) - .attributeDefinitions(AttributeDefinition.builder() - .attributeName(Accounts.ATTR_USERNAME_HASH) - .attributeType(ScalarAttributeType.B) - .build()) - .provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT) - .build(); - - dynamoDbExtension.getDynamoDbClient().createTable(createUsernamesTableRequest); @SuppressWarnings("unchecked") DynamicConfigurationManager m = mock(DynamicConfigurationManager.class); mockDynamicConfigManager = m; @@ -147,12 +91,12 @@ class AccountsTest { this.accounts = new Accounts( clock, - dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), - dynamoDbExtension.getTableName(), - NUMBER_CONSTRAINT_TABLE_NAME, - PNI_CONSTRAINT_TABLE_NAME, - USERNAME_CONSTRAINT_TABLE_NAME, + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Tables.ACCOUNTS.tableName(), + Tables.NUMBERS.tableName(), + Tables.PNI_ASSIGNMENTS.tableName(), + Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE); } @@ -247,7 +191,7 @@ class AccountsTest { final TransactWriteItem phoneNumberConstraintPut = TransactWriteItem.builder() .put( Put.builder() - .tableName(NUMBER_CONSTRAINT_TABLE_NAME) + .tableName(Tables.NUMBERS.tableName()) .item(Map.of( Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(account.getNumber()), Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(account.getUuid()))) @@ -264,7 +208,7 @@ class AccountsTest { final TransactWriteItem accountPut = TransactWriteItem.builder() .put(Put.builder() - .tableName(ACCOUNTS_TABLE_NAME) + .tableName(Tables.ACCOUNTS.tableName()) .conditionExpression("attribute_not_exists(#number) OR #number = :number") .expressionAttributeNames(Map.of("#number", Accounts.ATTR_ACCOUNT_E164)) .expressionAttributeValues(Map.of(":number", AttributeValues.fromString(account.getNumber()))) @@ -277,7 +221,7 @@ class AccountsTest { .build()) .build(); - dynamoDbExtension.getDynamoDbClient().transactWriteItems(TransactWriteItemsRequest.builder() + DYNAMO_DB_EXTENSION.getDynamoDbClient().transactWriteItems(TransactWriteItemsRequest.builder() .transactItems(phoneNumberConstraintPut, accountPut) .build()); } @@ -389,8 +333,8 @@ class AccountsTest { final DynamoDbAsyncClient dynamoDbAsyncClient = mock(DynamoDbAsyncClient.class); accounts = new Accounts(mock(DynamoDbClient.class), - dynamoDbAsyncClient, dynamoDbExtension.getTableName(), - NUMBER_CONSTRAINT_TABLE_NAME, PNI_CONSTRAINT_TABLE_NAME, USERNAME_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE); + dynamoDbAsyncClient, Tables.ACCOUNTS.tableName(), + Tables.NUMBERS.tableName(), Tables.PNI_ASSIGNMENTS.tableName(), Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE); Exception e = TransactionConflictException.builder().build(); e = wrapException ? new CompletionException(e) : e; @@ -612,8 +556,8 @@ class AccountsTest { final UUID existingPhoneNumberIdentifier = UUID.randomUUID(); // Artificially inject a conflicting PNI entry - dynamoDbExtension.getDynamoDbClient().putItem(PutItemRequest.builder() - .tableName(PNI_CONSTRAINT_TABLE_NAME) + DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() + .tableName(Tables.PNI_ASSIGNMENTS.tableName()) .item(Map.of( Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(existingPhoneNumberIdentifier), Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(existingAccountIdentifier))) @@ -649,9 +593,9 @@ class AccountsTest { accounts.confirmUsernameHash(account, USERNAME_HASH_2); assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty(); - assertThat(dynamoDbExtension.getDynamoDbClient() + assertThat(DYNAMO_DB_EXTENSION.getDynamoDbClient() .getItem(GetItemRequest.builder() - .tableName(USERNAME_CONSTRAINT_TABLE_NAME) + .tableName(Tables.USERNAMES.tableName()) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .build()) .item()).isEmpty(); @@ -775,9 +719,9 @@ class AccountsTest { assertArrayEquals(account1.getUsernameHash().orElseThrow(), USERNAME_HASH_1); assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account1.getUuid()); - final Map usernameConstraintRecord = dynamoDbExtension.getDynamoDbClient() + final Map usernameConstraintRecord = DYNAMO_DB_EXTENSION.getDynamoDbClient() .getItem(GetItemRequest.builder() - .tableName(USERNAME_CONSTRAINT_TABLE_NAME) + .tableName(Tables.USERNAMES.tableName()) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .build()) .item(); @@ -892,9 +836,9 @@ class AccountsTest { } private void assertPhoneNumberConstraintExists(final String number, final UUID uuid) { - final GetItemResponse numberConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( + final GetItemResponse numberConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem( GetItemRequest.builder() - .tableName(NUMBER_CONSTRAINT_TABLE_NAME) + .tableName(Tables.NUMBERS.tableName()) .key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number))) .build()); @@ -903,9 +847,9 @@ class AccountsTest { } private void assertPhoneNumberConstraintDoesNotExist(final String number) { - final GetItemResponse numberConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( + final GetItemResponse numberConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem( GetItemRequest.builder() - .tableName(NUMBER_CONSTRAINT_TABLE_NAME) + .tableName(Tables.NUMBERS.tableName()) .key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number))) .build()); @@ -913,9 +857,9 @@ class AccountsTest { } private void assertPhoneNumberIdentifierConstraintExists(final UUID phoneNumberIdentifier, final UUID uuid) { - final GetItemResponse pniConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( + final GetItemResponse pniConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem( GetItemRequest.builder() - .tableName(PNI_CONSTRAINT_TABLE_NAME) + .tableName(Tables.PNI_ASSIGNMENTS.tableName()) .key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier))) .build()); @@ -924,9 +868,9 @@ class AccountsTest { } private void assertPhoneNumberIdentifierConstraintDoesNotExist(final UUID phoneNumberIdentifier) { - final GetItemResponse pniConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( + final GetItemResponse pniConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem( GetItemRequest.builder() - .tableName(PNI_CONSTRAINT_TABLE_NAME) + .tableName(Tables.PNI_ASSIGNMENTS.tableName()) .key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier))) .build()); @@ -934,10 +878,10 @@ class AccountsTest { } private void verifyStoredState(String number, UUID uuid, UUID pni, byte[] usernameHash, Account expecting, boolean canonicallyDiscoverable) { - final DynamoDbClient db = dynamoDbExtension.getDynamoDbClient(); + final DynamoDbClient db = DYNAMO_DB_EXTENSION.getDynamoDbClient(); final GetItemResponse get = db.getItem(GetItemRequest.builder() - .tableName(dynamoDbExtension.getTableName()) + .tableName(Tables.ACCOUNTS.tableName()) .key(Map.of(Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(uuid))) .consistentRead(true) .build()); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManagerTest.java index 128b07216..c0cd7c54e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsManagerTest.java @@ -19,75 +19,27 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.function.Executable; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -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; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class DeletedAccountsManagerTest { - 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()) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .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()) - .globalSecondaryIndex(GlobalSecondaryIndex.builder() - .indexName(DeletedAccounts.UUID_TO_E164_INDEX_NAME) - .keySchema( - KeySchemaElement.builder().attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID).keyType(KeyType.HASH).build() - ) - .projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).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(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = + new DynamoDbExtension(Tables.DELETED_ACCOUNTS, Tables.DELETED_ACCOUNTS_LOCK); private DeletedAccounts deletedAccounts; private DeletedAccountsManager deletedAccountsManager; @BeforeEach void setUp() { - deletedAccounts = new DeletedAccounts(DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient(), - DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getTableName(), - NEEDS_RECONCILIATION_INDEX_NAME); + deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.DELETED_ACCOUNTS.tableName(), + Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName()); deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, - DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getLegacyDynamoClient(), - DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getTableName()); + DYNAMO_DB_EXTENSION.getLegacyDynamoClient(), + Tables.DELETED_ACCOUNTS_LOCK.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsTest.java index a48b161db..8b75d0cfe 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DeletedAccountsTest.java @@ -16,62 +16,22 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.util.Pair; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -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 DeletedAccountsTest { - private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test"; - @RegisterExtension - static DynamoDbExtension dynamoDbExtension = 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()) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .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()) - .globalSecondaryIndex(GlobalSecondaryIndex.builder() - .indexName(DeletedAccounts.UUID_TO_E164_INDEX_NAME) - .keySchema( - KeySchemaElement.builder().attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID).keyType(KeyType.HASH).build() - ) - .projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build()) - .provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build()) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.DELETED_ACCOUNTS); private DeletedAccounts deletedAccounts; @BeforeEach void setUp() { - deletedAccounts = new DeletedAccounts(dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getTableName(), - NEEDS_RECONCILIATION_INDEX_NAME); + deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.DELETED_ACCOUNTS.tableName(), + Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java index 36af6bf1c..a8af12fe5 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtension.java @@ -1,3 +1,8 @@ +/* + * Copyright 2021-2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + package org.whispersystems.textsecuregcm.storage; import com.almworks.sqlite4java.SQLite; @@ -31,7 +36,23 @@ import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback { - static final String DEFAULT_TABLE_NAME = "test_table"; + public interface TableSchema { + public String tableName(); + public String hashKeyName(); + public String rangeKeyName(); + public List attributeDefinitions(); + public List globalSecondaryIndexes(); + public List localSecondaryIndexes(); + } + + record RawSchema( + String tableName, + String hashKeyName, + String rangeKeyName, + List attributeDefinitions, + List globalSecondaryIndexes, + List localSecondaryIndexes + ) implements TableSchema { } static final ProvisionedThroughput DEFAULT_PROVISIONED_THROUGHPUT = ProvisionedThroughput.builder() .readCapacityUnits(20L) @@ -42,42 +63,14 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback private DynamoDBProxyServer server; private int port; - - private final String tableName; - private final String hashKeyName; - private final String rangeKeyName; - - private final List attributeDefinitions; - private final List globalSecondaryIndexes; - private final List localSecondaryIndexes; - - private final long readCapacityUnits; - private final long writeCapacityUnits; - + + private final List schemas; private DynamoDbClient dynamoDB2; private DynamoDbAsyncClient dynamoAsyncDB2; private AmazonDynamoDB legacyDynamoClient; - private DynamoDbExtension(String tableName, String hashKey, String rangeKey, - List attributeDefinitions, List globalSecondaryIndexes, - final List localSecondaryIndexes, - long readCapacityUnits, - long writeCapacityUnits) { - - this.tableName = tableName; - this.hashKeyName = hashKey; - this.rangeKeyName = rangeKey; - this.localSecondaryIndexes = localSecondaryIndexes; - - this.readCapacityUnits = readCapacityUnits; - this.writeCapacityUnits = writeCapacityUnits; - - this.attributeDefinitions = attributeDefinitions; - this.globalSecondaryIndexes = globalSecondaryIndexes; - } - - public static DynamoDbExtensionBuilder builder() { - return new DynamoDbExtensionBuilder(); + public DynamoDbExtension(TableSchema... schemas) { + this.schemas = List.of(schemas); } private static void loadLibrary() { @@ -114,32 +107,33 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback initializeClient(); - createTable(); + createTables(); } - private void createTable() { + private void createTables() { + schemas.stream().forEach(this::createTable); + } + + private void createTable(TableSchema schema) { KeySchemaElement[] keySchemaElements; - if (rangeKeyName == null) { + if (schema.rangeKeyName() == null) { keySchemaElements = new KeySchemaElement[] { - KeySchemaElement.builder().attributeName(hashKeyName).keyType(KeyType.HASH).build(), + KeySchemaElement.builder().attributeName(schema.hashKeyName()).keyType(KeyType.HASH).build(), }; } else { keySchemaElements = new KeySchemaElement[] { - KeySchemaElement.builder().attributeName(hashKeyName).keyType(KeyType.HASH).build(), - KeySchemaElement.builder().attributeName(rangeKeyName).keyType(KeyType.RANGE).build(), + KeySchemaElement.builder().attributeName(schema.hashKeyName()).keyType(KeyType.HASH).build(), + KeySchemaElement.builder().attributeName(schema.rangeKeyName()).keyType(KeyType.RANGE).build(), }; } final CreateTableRequest createTableRequest = CreateTableRequest.builder() - .tableName(tableName) + .tableName(schema.tableName()) .keySchema(keySchemaElements) - .attributeDefinitions(attributeDefinitions.isEmpty() ? null : attributeDefinitions) - .globalSecondaryIndexes(globalSecondaryIndexes.isEmpty() ? null : globalSecondaryIndexes) - .localSecondaryIndexes(localSecondaryIndexes.isEmpty() ? null : localSecondaryIndexes) - .provisionedThroughput(ProvisionedThroughput.builder() - .readCapacityUnits(readCapacityUnits) - .writeCapacityUnits(writeCapacityUnits) - .build()) + .attributeDefinitions(schema.attributeDefinitions().isEmpty() ? null : schema.attributeDefinitions()) + .globalSecondaryIndexes(schema.globalSecondaryIndexes().isEmpty() ? null : schema.globalSecondaryIndexes()) + .localSecondaryIndexes(schema.localSecondaryIndexes().isEmpty() ? null : schema.localSecondaryIndexes()) + .provisionedThroughput(DEFAULT_PROVISIONED_THROUGHPUT) .build(); getDynamoDbClient().createTable(createTableRequest); @@ -178,60 +172,6 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback .build(); } - public static class DynamoDbExtensionBuilder { - - private String tableName = DEFAULT_TABLE_NAME; - - private String hashKey; - private String rangeKey; - - private final List attributeDefinitions = new ArrayList<>(); - private final List globalSecondaryIndexes = new ArrayList<>(); - private final List localSecondaryIndexes = new ArrayList<>(); - - private final long readCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.readCapacityUnits(); - private final long writeCapacityUnits = DEFAULT_PROVISIONED_THROUGHPUT.writeCapacityUnits(); - - private DynamoDbExtensionBuilder() { - - } - - public DynamoDbExtensionBuilder tableName(String databaseName) { - this.tableName = databaseName; - return this; - } - - public DynamoDbExtensionBuilder hashKey(String hashKey) { - this.hashKey = hashKey; - return this; - } - - public DynamoDbExtensionBuilder rangeKey(String rangeKey) { - this.rangeKey = rangeKey; - return this; - } - - public DynamoDbExtensionBuilder attributeDefinition(AttributeDefinition attributeDefinition) { - attributeDefinitions.add(attributeDefinition); - return this; - } - - public DynamoDbExtensionBuilder globalSecondaryIndex(GlobalSecondaryIndex index) { - globalSecondaryIndexes.add(index); - return this; - } - - public DynamoDbExtensionBuilder localSecondaryIndex(LocalSecondaryIndex index) { - localSecondaryIndexes.add(index); - return this; - } - - public DynamoDbExtension build() { - return new DynamoDbExtension(tableName, hashKey, rangeKey, - attributeDefinitions, globalSecondaryIndexes, localSecondaryIndexes, readCapacityUnits, writeCapacityUnits); - } - } - public DynamoDbClient getDynamoDbClient() { return dynamoDB2; } @@ -243,8 +183,4 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback public AmazonDynamoDB getLegacyDynamoClient() { return legacyDynamoClient; } - - public String getTableName() { - return tableName; - } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtensionSchema.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtensionSchema.java new file mode 100644 index 000000000..2eb595738 --- /dev/null +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/DynamoDbExtensionSchema.java @@ -0,0 +1,336 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.whispersystems.textsecuregcm.storage; + +import java.util.List; +import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +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.LocalSecondaryIndex; +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; + +public final class DynamoDbExtensionSchema { + + public enum Indexes { + + DELETED_ACCOUNTS_NEEDS_RECONCILIATION("needs_reconciliation_test"); + + private final String name; + + public String indexName() { + return name; + } + + Indexes(final String name) { this.name = name; } + + } + + public enum Tables implements DynamoDbExtension.TableSchema { + + ACCOUNTS("accounts_test", + Accounts.KEY_ACCOUNT_UUID, + null, + List.of(AttributeDefinition.builder() + .attributeName(Accounts.KEY_ACCOUNT_UUID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + DELETED_ACCOUNTS("deleted_accounts_test", + DeletedAccounts.KEY_ACCOUNT_E164, + null, + List.of( + AttributeDefinition.builder() + .attributeName(DeletedAccounts.KEY_ACCOUNT_E164) + .attributeType(ScalarAttributeType.S).build(), + AttributeDefinition.builder() + .attributeName(DeletedAccounts.ATTR_NEEDS_CDS_RECONCILIATION) + .attributeType(ScalarAttributeType.N) + .build(), + AttributeDefinition.builder() + .attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of( + GlobalSecondaryIndex.builder() + .indexName(Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName()) + .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(), + GlobalSecondaryIndex.builder() + .indexName(DeletedAccounts.UUID_TO_E164_INDEX_NAME) + .keySchema( + KeySchemaElement.builder().attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID).keyType(KeyType.HASH).build() + ) + .projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build()) + .provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build()) + .build()), + List.of() + ), + + DELETED_ACCOUNTS_LOCK("deleted_accounts_lock_test", + DeletedAccounts.KEY_ACCOUNT_E164, + null, + List.of(AttributeDefinition.builder() + .attributeName(DeletedAccounts.KEY_ACCOUNT_E164) + .attributeType(ScalarAttributeType.S).build()), + List.of(), List.of()), + + NUMBERS("numbers_test", + Accounts.ATTR_ACCOUNT_E164, + null, + List.of(AttributeDefinition.builder() + .attributeName(Accounts.ATTR_ACCOUNT_E164) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + KEYS("keys_test", + Keys.KEY_ACCOUNT_UUID, + Keys.KEY_DEVICE_ID_KEY_ID, + List.of( + AttributeDefinition.builder() + .attributeName(Keys.KEY_ACCOUNT_UUID) + .attributeType(ScalarAttributeType.B) + .build(), + AttributeDefinition.builder() + .attributeName(Keys.KEY_DEVICE_ID_KEY_ID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + PNI("pni_test", + PhoneNumberIdentifiers.KEY_E164, + null, + List.of( + AttributeDefinition.builder() + .attributeName(PhoneNumberIdentifiers.KEY_E164) + .attributeType(ScalarAttributeType.S) + .build(), + AttributeDefinition.builder() + .attributeName(PhoneNumberIdentifiers.ATTR_PHONE_NUMBER_IDENTIFIER) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(GlobalSecondaryIndex.builder() + .indexName(PhoneNumberIdentifiers.INDEX_NAME) + .projection(Projection.builder() + .projectionType(ProjectionType.KEYS_ONLY) + .build()) + .keySchema(KeySchemaElement.builder().keyType(KeyType.HASH) + .attributeName(PhoneNumberIdentifiers.ATTR_PHONE_NUMBER_IDENTIFIER) + .build()) + .provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build()) + .build()), + List.of()), + + PNI_ASSIGNMENTS("pni_assignment_test", + Accounts.ATTR_PNI_UUID, + null, + List.of(AttributeDefinition.builder() + .attributeName(Accounts.ATTR_PNI_UUID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + ISSUED_RECEIPTS("issued_receipts_test", + IssuedReceiptsManager.KEY_PROCESSOR_ITEM_ID, + null, + List.of(AttributeDefinition.builder() + .attributeName(IssuedReceiptsManager.KEY_PROCESSOR_ITEM_ID) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + MESSAGES("messages_test", + MessagesDynamoDb.KEY_PARTITION, + MessagesDynamoDb.KEY_SORT, + List.of( + AttributeDefinition.builder().attributeName(MessagesDynamoDb.KEY_PARTITION).attributeType(ScalarAttributeType.B).build(), + AttributeDefinition.builder().attributeName(MessagesDynamoDb.KEY_SORT).attributeType(ScalarAttributeType.B).build(), + AttributeDefinition.builder().attributeName(MessagesDynamoDb.LOCAL_INDEX_MESSAGE_UUID_KEY_SORT) + .attributeType(ScalarAttributeType.B).build()), + List.of(), + List.of(LocalSecondaryIndex.builder() + .indexName(MessagesDynamoDb.LOCAL_INDEX_MESSAGE_UUID_NAME) + .keySchema( + KeySchemaElement.builder().attributeName(MessagesDynamoDb.KEY_PARTITION).keyType(KeyType.HASH).build(), + KeySchemaElement.builder() + .attributeName(MessagesDynamoDb.LOCAL_INDEX_MESSAGE_UUID_KEY_SORT) + .keyType(KeyType.RANGE) + .build()) + .projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build()) + .build())), + + PROFILES("profiles_test", + Profiles.KEY_ACCOUNT_UUID, + Profiles.ATTR_VERSION, + List.of( + AttributeDefinition.builder() + .attributeName(Profiles.KEY_ACCOUNT_UUID) + .attributeType(ScalarAttributeType.B) + .build(), + AttributeDefinition.builder() + .attributeName(Profiles.ATTR_VERSION) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + PUSH_CHALLENGES("push_challenge_test", + PushChallengeDynamoDb.KEY_ACCOUNT_UUID, + null, + List.of(AttributeDefinition.builder() + .attributeName(PushChallengeDynamoDb.KEY_ACCOUNT_UUID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + REDEEMED_RECEIPTS("redeemed_receipts_test", + RedeemedReceiptsManager.KEY_SERIAL, + null, + List.of(AttributeDefinition.builder() + .attributeName(RedeemedReceiptsManager.KEY_SERIAL) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + REGISTRATION_RECOVERY_PASSWORDS("registration_recovery_passwords_test", + RegistrationRecoveryPasswords.KEY_E164, + null, + List.of(AttributeDefinition.builder() + .attributeName(RegistrationRecoveryPasswords.KEY_E164) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + REMOTE_CONFIGS("remote_configs_test", + RemoteConfigs.KEY_NAME, + null, + List.of(AttributeDefinition.builder() + .attributeName(RemoteConfigs.KEY_NAME) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + REPORT_MESSAGES("report_messages_test", + ReportMessageDynamoDb.KEY_HASH, + null, + List.of(AttributeDefinition.builder() + .attributeName(ReportMessageDynamoDb.KEY_HASH) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + SUBSCRIPTIONS("subscriptions_test", + SubscriptionManager.KEY_USER, + null, + List.of( + AttributeDefinition.builder() + .attributeName(SubscriptionManager.KEY_USER) + .attributeType(ScalarAttributeType.B) + .build(), + AttributeDefinition.builder() + .attributeName(SubscriptionManager.KEY_PROCESSOR_ID_CUSTOMER_ID) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(GlobalSecondaryIndex.builder() + .indexName(SubscriptionManager.INDEX_NAME) + .keySchema(KeySchemaElement.builder() + .attributeName(SubscriptionManager.KEY_PROCESSOR_ID_CUSTOMER_ID) + .keyType(KeyType.HASH) + .build()) + .projection(Projection.builder() + .projectionType(ProjectionType.KEYS_ONLY) + .build()) + .provisionedThroughput(ProvisionedThroughput.builder() + .readCapacityUnits(20L) + .writeCapacityUnits(20L) + .build()) + .build()), + List.of()), + + USERNAMES("usernames_test", + Accounts.ATTR_USERNAME_HASH, + null, + List.of(AttributeDefinition.builder() + .attributeName(Accounts.ATTR_USERNAME_HASH) + .attributeType(ScalarAttributeType.B) + .build()), + List.of(), List.of()), + + VERIFICATION_CODES("verification_codes_test", + VerificationCodeStore.KEY_E164, + null, + List.of(AttributeDefinition.builder() + .attributeName(VerificationCodeStore.KEY_E164) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()), + + VERIFICATION_SESSIONS("verification_sessions_test", + VerificationSessions.KEY_KEY, + null, + List.of(AttributeDefinition.builder() + .attributeName(VerificationSessions.KEY_KEY) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), List.of()); + + private final String tableName; + private final String hashKeyName; + private final String rangeKeyName; + private final List attributeDefinitions; + private final List globalSecondaryIndexes; + private final List localSecondaryIndexes; + + Tables( + final String tableName, + final String hashKeyName, + final String rangeKeyName, + final List attributeDefinitions, + final List globalSecondaryIndexes, + final List localSecondaryIndexes + ) { + this.tableName = tableName; + this.hashKeyName = hashKeyName; + this.rangeKeyName = rangeKeyName; + this.attributeDefinitions = attributeDefinitions; + this.globalSecondaryIndexes = globalSecondaryIndexes; + this.localSecondaryIndexes = localSecondaryIndexes; + } + + public String tableName() { + return tableName; + } + + public String hashKeyName() { + return hashKeyName; + } + + public String rangeKeyName() { + return rangeKeyName; + } + + public List attributeDefinitions() { + return attributeDefinitions; + } + + public List globalSecondaryIndexes() { + return globalSecondaryIndexes; + } + + public List localSecondaryIndexes() { + return localSecondaryIndexes; + } + + } + +} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManagerTest.java index de042b9a1..c3cf5fb80 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/IssuedReceiptsManagerTest.java @@ -20,24 +20,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class IssuedReceiptsManagerTest { private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; - private static final String ISSUED_RECEIPTS_TABLE_NAME = "issued_receipts"; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(ISSUED_RECEIPTS_TABLE_NAME) - .hashKey(IssuedReceiptsManager.KEY_PROCESSOR_ITEM_ID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(IssuedReceiptsManager.KEY_PROCESSOR_ITEM_ID) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.ISSUED_RECEIPTS); ReceiptCredentialRequest receiptCredentialRequest; IssuedReceiptsManager issuedReceiptsManager; @@ -48,9 +39,9 @@ class IssuedReceiptsManagerTest { byte[] generator = new byte[16]; SECURE_RANDOM.nextBytes(generator); issuedReceiptsManager = new IssuedReceiptsManager( - ISSUED_RECEIPTS_TABLE_NAME, + Tables.ISSUED_RECEIPTS.tableName(), Duration.ofDays(90), - dynamoDbExtension.getDynamoDbAsyncClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), generator); } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java index 4111dacb9..6c21e4f1f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/KeysTest.java @@ -15,38 +15,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.entities.PreKey; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; class KeysTest { - private static final String TABLE_NAME = "Signal_Keys_Test"; - private Keys keys; @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(Keys.KEY_ACCOUNT_UUID) - .rangeKey(Keys.KEY_DEVICE_ID_KEY_ID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Keys.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .attributeDefinition( - AttributeDefinition.builder() - .attributeName(Keys.KEY_DEVICE_ID_KEY_ID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.KEYS); private static final UUID ACCOUNT_UUID = UUID.randomUUID(); private static final long DEVICE_ID = 1L; @BeforeEach void setup() { - keys = new Keys(dynamoDbExtension.getDynamoDbClient(), TABLE_NAME); + keys = new Keys(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.KEYS.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagePersisterIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagePersisterIntegrationTest.java index 79a0255f6..e46418ee9 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagePersisterIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagePersisterIntegrationTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; -import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; @@ -44,7 +44,7 @@ import software.amazon.awssdk.services.dynamodb.model.ScanRequest; class MessagePersisterIntegrationTest { @RegisterExtension - static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES); @RegisterExtension static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); @@ -74,8 +74,8 @@ class MessagePersisterIntegrationTest { messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); messageDeletionExecutorService = Executors.newSingleThreadExecutor(); - final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(14), + final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14), messageDeletionExecutorService); final AccountsManager accountsManager = mock(AccountsManager.class); @@ -164,10 +164,10 @@ class MessagePersisterIntegrationTest { messagePersister.stop(); - DynamoDbClient dynamoDB = dynamoDbExtension.getDynamoDbClient(); + DynamoDbClient dynamoDB = DYNAMO_DB_EXTENSION.getDynamoDbClient(); final List persistedMessages = - dynamoDB.scan(ScanRequest.builder().tableName(MessagesDynamoDbExtension.TABLE_NAME).build()).items().stream() + dynamoDB.scan(ScanRequest.builder().tableName(Tables.MESSAGES.tableName()).build()).items().stream() .map(item -> { try { return MessagesDynamoDb.convertItemToEnvelope(item); diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDbTest.java index 7d76b1069..38e92820e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDbTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/MessagesDynamoDbTest.java @@ -26,8 +26,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.reactivestreams.Publisher; import org.whispersystems.textsecuregcm.entities.MessageProtos; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.tests.util.MessageHelper; -import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; @@ -77,13 +77,13 @@ class MessagesDynamoDbTest { @RegisterExtension - static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES); @BeforeEach void setup() { messageDeletionExecutorService = Executors.newSingleThreadExecutor(); - messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(14), + messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14), messageDeletionExecutorService); } @@ -170,7 +170,7 @@ class MessagesDynamoDbTest { .thenRequest(halfOfMessageLoadLimit) .expectNextCount(halfOfMessageLoadLimit) // the first 100 should be fetched and buffered, but further requests should fail - .then(() -> dynamoDbExtension.stopServer()) + .then(() -> DYNAMO_DB_EXTENSION.stopServer()) .thenRequest(halfOfMessageLoadLimit) .expectNextCount(halfOfMessageLoadLimit) // we’ve consumed all the buffered messages, so a single request will fail diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PhoneNumberIdentifiersTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PhoneNumberIdentifiersTest.java index edb858ac9..174465a3b 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PhoneNumberIdentifiersTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PhoneNumberIdentifiersTest.java @@ -14,48 +14,19 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; 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.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; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class PhoneNumberIdentifiersTest { - private static final String PNI_TABLE_NAME = "pni_test"; - @RegisterExtension - static DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() - .tableName(PNI_TABLE_NAME) - .hashKey(PhoneNumberIdentifiers.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(PhoneNumberIdentifiers.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(PhoneNumberIdentifiers.ATTR_PHONE_NUMBER_IDENTIFIER) - .attributeType(ScalarAttributeType.B) - .build()) - .globalSecondaryIndex(GlobalSecondaryIndex.builder() - .indexName(PhoneNumberIdentifiers.INDEX_NAME) - .projection(Projection.builder() - .projectionType(ProjectionType.KEYS_ONLY) - .build()) - .keySchema(KeySchemaElement.builder().keyType(KeyType.HASH) - .attributeName(PhoneNumberIdentifiers.ATTR_PHONE_NUMBER_IDENTIFIER) - .build()) - .provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build()) - .build()) - .build(); + static DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PNI); private PhoneNumberIdentifiers phoneNumberIdentifiers; @BeforeEach void setUp() { - phoneNumberIdentifiers = new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), PNI_TABLE_NAME); + phoneNumberIdentifiers = new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.PNI.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java index 328a24211..e85b8661c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ProfilesTest.java @@ -12,9 +12,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.whispersystems.textsecuregcm.util.AttributeValues; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -27,30 +26,16 @@ import java.util.stream.Stream; public abstract class ProfilesTest { - private static final String PROFILES_TABLE_NAME = "profiles_test"; - @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(PROFILES_TABLE_NAME) - .hashKey(Profiles.KEY_ACCOUNT_UUID) - .rangeKey(Profiles.ATTR_VERSION) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Profiles.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(Profiles.ATTR_VERSION) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PROFILES); private Profiles profiles; @BeforeEach void setUp() { - profiles = new Profiles(dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), - PROFILES_TABLE_NAME); + profiles = new Profiles(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Tables.PROFILES.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PushChallengeDynamoDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PushChallengeDynamoDbTest.java index 5da07d2c2..0dd06c4f3 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/PushChallengeDynamoDbTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/PushChallengeDynamoDbTest.java @@ -18,8 +18,7 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; 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; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class PushChallengeDynamoDbTest { @@ -28,22 +27,16 @@ class PushChallengeDynamoDbTest { private static final long CURRENT_TIME_MILLIS = 1_000_000_000; private static final Random RANDOM = new Random(); - private static final String TABLE_NAME = "push_challenge_test"; @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(PushChallengeDynamoDb.KEY_ACCOUNT_UUID) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(PushChallengeDynamoDb.KEY_ACCOUNT_UUID) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PUSH_CHALLENGES); @BeforeEach void setUp() { - this.pushChallengeDynamoDb = new PushChallengeDynamoDb(dynamoDbExtension.getDynamoDbClient(), TABLE_NAME, Clock.fixed( - Instant.ofEpochMilli(CURRENT_TIME_MILLIS), ZoneId.systemDefault())); + this.pushChallengeDynamoDb = new PushChallengeDynamoDb( + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.PUSH_CHALLENGES.tableName(), + Clock.fixed(Instant.ofEpochMilli(CURRENT_TIME_MILLIS), ZoneId.systemDefault())); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/RegistrationRecoveryTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/RegistrationRecoveryTest.java index 888716dbc..1763fb977 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/RegistrationRecoveryTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/RegistrationRecoveryTest.java @@ -21,17 +21,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; import org.whispersystems.textsecuregcm.util.AttributeValues; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.util.MockUtils; import org.whispersystems.textsecuregcm.util.MutableClock; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; public class RegistrationRecoveryTest { - private static final String TABLE_NAME = "registration_recovery_passwords"; - private static final MutableClock CLOCK = MockUtils.mutableClock(0); private static final Duration EXPIRATION = Duration.ofSeconds(1000); @@ -43,14 +40,8 @@ public class RegistrationRecoveryTest { private static final SaltedTokenHash ANOTHER_HASH = SaltedTokenHash.generateFor("pass2"); @RegisterExtension - private static final DynamoDbExtension DB_EXTENSION = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(RegistrationRecoveryPasswords.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(RegistrationRecoveryPasswords.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + private static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + Tables.REGISTRATION_RECOVERY_PASSWORDS); private RegistrationRecoveryPasswords store; @@ -60,10 +51,10 @@ public class RegistrationRecoveryTest { public void before() throws Exception { CLOCK.setTimeMillis(Clock.systemUTC().millis()); store = new RegistrationRecoveryPasswords( - DB_EXTENSION.getTableName(), + Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName(), EXPIRATION, - DB_EXTENSION.getDynamoDbClient(), - DB_EXTENSION.getDynamoDbAsyncClient(), + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), CLOCK ); manager = new RegistrationRecoveryPasswordsManager(store); @@ -147,8 +138,8 @@ public class RegistrationRecoveryTest { } private static long fetchTimestamp(final String number) throws ExecutionException, InterruptedException { - return DB_EXTENSION.getDynamoDbAsyncClient().getItem(GetItemRequest.builder() - .tableName(DB_EXTENSION.getTableName()) + return DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient().getItem(GetItemRequest.builder() + .tableName(Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName()) .key(Map.of(RegistrationRecoveryPasswords.KEY_E164, AttributeValues.fromString(number))) .build()) .thenApply(getItemResponse -> { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/RemoteConfigsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/RemoteConfigsTest.java index 9fcebb562..d3db9ccbe 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/RemoteConfigsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/RemoteConfigsTest.java @@ -9,8 +9,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import java.util.List; import java.util.Set; @@ -18,23 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat; class RemoteConfigsTest { - private static final String REMOTE_CONFIGS_TABLE_NAME = "remote_configs_test"; - @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(REMOTE_CONFIGS_TABLE_NAME) - .hashKey(RemoteConfigs.KEY_NAME) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(RemoteConfigs.KEY_NAME) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REMOTE_CONFIGS); private RemoteConfigs remoteConfigs; @BeforeEach void setUp() { - remoteConfigs = new RemoteConfigs(dynamoDbExtension.getDynamoDbClient(), REMOTE_CONFIGS_TABLE_NAME); + remoteConfigs = new RemoteConfigs(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.REMOTE_CONFIGS.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ReportMessageDynamoDbTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ReportMessageDynamoDbTest.java index 5281ea240..89cd11b02 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/ReportMessageDynamoDbTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/ReportMessageDynamoDbTest.java @@ -14,30 +14,23 @@ import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.util.UUIDUtil; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; class ReportMessageDynamoDbTest { private ReportMessageDynamoDb reportMessageDynamoDb; - private static final String TABLE_NAME = "report_message_test"; - @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(ReportMessageDynamoDb.KEY_HASH) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(ReportMessageDynamoDb.KEY_HASH) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REPORT_MESSAGES); @BeforeEach void setUp() { - this.reportMessageDynamoDb = new ReportMessageDynamoDb(dynamoDbExtension.getDynamoDbClient(), TABLE_NAME, Duration.ofDays(1)); + this.reportMessageDynamoDb = new ReportMessageDynamoDb( + DYNAMO_DB_EXTENSION.getDynamoDbClient(), + Tables.REPORT_MESSAGES.tableName(), + Duration.ofDays(1)); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStoreTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStoreTest.java index e4d7398a1..4205e771f 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStoreTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/SerializedExpireableJsonDynamoStoreTest.java @@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; @@ -39,14 +40,17 @@ class SerializedExpireableJsonDynamoStoreTest { } @RegisterExtension - static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(SerializedExpireableJsonDynamoStore.KEY_KEY) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(SerializedExpireableJsonDynamoStore.KEY_KEY) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension( + new DynamoDbExtension.RawSchema( + TABLE_NAME, + SerializedExpireableJsonDynamoStore.KEY_KEY, + null, + List.of(AttributeDefinition.builder() + .attributeName(SerializedExpireableJsonDynamoStore.KEY_KEY) + .attributeType(ScalarAttributeType.S) + .build()), + List.of(), + List.of())); private SerializedExpireableJsonDynamoStore store; diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/SubscriptionManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/SubscriptionManagerTest.java index 0a12db09d..81bf6348d 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/SubscriptionManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/SubscriptionManagerTest.java @@ -28,49 +28,16 @@ import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult; import org.whispersystems.textsecuregcm.storage.SubscriptionManager.Record; import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -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; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class SubscriptionManagerTest { private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(3); - private static final String SUBSCRIPTIONS_TABLE_NAME = "subscriptions"; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder(). - tableName(SUBSCRIPTIONS_TABLE_NAME). - hashKey(SubscriptionManager.KEY_USER). - attributeDefinition(AttributeDefinition.builder(). - attributeName(SubscriptionManager.KEY_USER). - attributeType(ScalarAttributeType.B). - build()). - attributeDefinition(AttributeDefinition.builder(). - attributeName(SubscriptionManager.KEY_PROCESSOR_ID_CUSTOMER_ID). - attributeType(ScalarAttributeType.B). - build()). - globalSecondaryIndex(GlobalSecondaryIndex.builder(). - indexName(SubscriptionManager.INDEX_NAME). - keySchema(KeySchemaElement.builder(). - attributeName(SubscriptionManager.KEY_PROCESSOR_ID_CUSTOMER_ID). - keyType(KeyType.HASH). - build()). - projection(Projection.builder(). - projectionType(ProjectionType.KEYS_ONLY). - build()). - provisionedThroughput(ProvisionedThroughput.builder(). - readCapacityUnits(20L). - writeCapacityUnits(20L). - build()). - build()). - build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.SUBSCRIPTIONS); byte[] user; byte[] password; @@ -85,7 +52,7 @@ class SubscriptionManagerTest { customer = Base64.getEncoder().encodeToString(getRandomBytes(16)); created = Instant.ofEpochSecond(NOW_EPOCH_SECONDS); subscriptionManager = new SubscriptionManager( - SUBSCRIPTIONS_TABLE_NAME, dynamoDbExtension.getDynamoDbAsyncClient()); + Tables.SUBSCRIPTIONS.tableName(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java index 3e541ba35..94fa6f3bb 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationCodeStoreTest.java @@ -19,15 +19,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; class VerificationCodeStoreTest { private VerificationCodeStore verificationCodeStore; - private static final String TABLE_NAME = "verification_code_test"; - private static final String PHONE_NUMBER = "+14151112222"; private static final long VALID_TIMESTAMP = Instant.now().toEpochMilli(); @@ -35,18 +32,12 @@ class VerificationCodeStoreTest { Duration.ofHours(1)).toEpochMilli(); @RegisterExtension - static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(VerificationCodeStore.KEY_E164) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(VerificationCodeStore.KEY_E164) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.VERIFICATION_CODES); @BeforeEach void setUp() { - verificationCodeStore = new VerificationCodeStore(DYNAMO_DB_EXTENSION.getDynamoDbClient(), TABLE_NAME); + verificationCodeStore = new VerificationCodeStore( + DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.VERIFICATION_CODES.tableName()); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java index 047d550ef..4d6042b85 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/storage/VerificationSessionsTest.java @@ -21,32 +21,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.whispersystems.textsecuregcm.registration.VerificationSession; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.util.ExceptionUtils; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; class VerificationSessionsTest { - private static final String TABLE_NAME = "verification_sessions_test"; - private static final Clock clock = Clock.systemUTC(); @RegisterExtension - static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey(VerificationSessions.KEY_KEY) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(VerificationSessions.KEY_KEY) - .attributeType(ScalarAttributeType.S) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.VERIFICATION_SESSIONS); private VerificationSessions verificationSessions; @BeforeEach void setUp() { - verificationSessions = new VerificationSessions(DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), TABLE_NAME, clock); + verificationSessions = new VerificationSessions( + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.VERIFICATION_SESSIONS.tableName(), clock); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/RedeemedReceiptsManagerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/RedeemedReceiptsManagerTest.java index 3d737f5dc..8170ba63c 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/RedeemedReceiptsManagerTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/tests/storage/RedeemedReceiptsManagerTest.java @@ -21,27 +21,18 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.receipts.ReceiptSerial; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager; import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.util.TestClock; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; class RedeemedReceiptsManagerTest { private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; - private static final String REDEEMED_RECEIPTS_TABLE_NAME = "redeemed_receipts"; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @RegisterExtension - static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() - .tableName(REDEEMED_RECEIPTS_TABLE_NAME) - .hashKey(RedeemedReceiptsManager.KEY_SERIAL) - .attributeDefinition(AttributeDefinition.builder() - .attributeName(RedeemedReceiptsManager.KEY_SERIAL) - .attributeType(ScalarAttributeType.B) - .build()) - .build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REDEEMED_RECEIPTS); Clock clock = TestClock.pinned(Instant.ofEpochSecond(NOW_EPOCH_SECONDS)); ReceiptSerial receiptSerial; @@ -53,7 +44,10 @@ class RedeemedReceiptsManagerTest { SECURE_RANDOM.nextBytes(receiptSerialBytes); receiptSerial = new ReceiptSerial(receiptSerialBytes); redeemedReceiptsManager = new RedeemedReceiptsManager( - clock, REDEEMED_RECEIPTS_TABLE_NAME, dynamoDbExtension.getDynamoDbAsyncClient(), Duration.ofDays(90)); + clock, + Tables.REDEEMED_RECEIPTS.tableName(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), + Duration.ofDays(90)); } @Test diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/MessagesDynamoDbExtension.java b/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/MessagesDynamoDbExtension.java deleted file mode 100644 index 1aeba95e1..000000000 --- a/service/src/test/java/org/whispersystems/textsecuregcm/tests/util/MessagesDynamoDbExtension.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.tests.util; - -import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; -import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; -import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; -import software.amazon.awssdk.services.dynamodb.model.KeyType; -import software.amazon.awssdk.services.dynamodb.model.LocalSecondaryIndex; -import software.amazon.awssdk.services.dynamodb.model.Projection; -import software.amazon.awssdk.services.dynamodb.model.ProjectionType; -import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; - -public class MessagesDynamoDbExtension { - - public static final String TABLE_NAME = "Signal_Messages_UnitTest"; - - public static DynamoDbExtension build() { - return DynamoDbExtension.builder() - .tableName(TABLE_NAME) - .hashKey("H") - .rangeKey("S") - .attributeDefinition( - AttributeDefinition.builder().attributeName("H").attributeType(ScalarAttributeType.B).build()) - .attributeDefinition( - AttributeDefinition.builder().attributeName("S").attributeType(ScalarAttributeType.B).build()) - .attributeDefinition( - AttributeDefinition.builder().attributeName("U").attributeType(ScalarAttributeType.B).build()) - .localSecondaryIndex(LocalSecondaryIndex.builder().indexName("Message_UUID_Index") - .keySchema(KeySchemaElement.builder().attributeName("H").keyType(KeyType.HASH).build(), - KeySchemaElement.builder().attributeName("U").keyType(KeyType.RANGE).build()) - .projection(Projection.builder().projectionType(ProjectionType.KEYS_ONLY).build()) - .build()) - .build(); - } - -} diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java index d13830bc3..2a7e98e8e 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnectionIntegrationTest.java @@ -51,11 +51,11 @@ import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; +import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables; import org.whispersystems.textsecuregcm.storage.MessagesCache; import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.ReportMessageManager; -import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension; import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.websocket.WebSocketClient; import org.whispersystems.websocket.messages.WebSocketResponseMessage; @@ -65,7 +65,7 @@ import reactor.core.scheduler.Schedulers; class WebSocketConnectionIntegrationTest { @RegisterExtension - static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); + static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES); @RegisterExtension static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); @@ -90,8 +90,8 @@ class WebSocketConnectionIntegrationTest { messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(), REDIS_CLUSTER_EXTENSION.getRedisCluster(), sharedExecutorService, messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC()); - messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), - dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(7), + messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(), + DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(7), sharedExecutorService); reportMessageManager = mock(ReportMessageManager.class); account = mock(Account.class);