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 <jkt@viola.signal.org>
This commit is contained in:
Jonathan Klabunde Tomer 2023-04-03 13:08:43 -07:00 committed by GitHub
parent f5726f63bd
commit 48ebafa4e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 574 additions and 827 deletions

View File

@ -45,11 +45,17 @@ import software.amazon.awssdk.services.dynamodb.model.WriteRequest;
public class MessagesDynamoDb extends AbstractDynamoDbStore { public class MessagesDynamoDb extends AbstractDynamoDbStore {
private static final String KEY_PARTITION = "H"; @VisibleForTesting
private static final String KEY_SORT = "S"; static final String KEY_PARTITION = "H";
private static final String LOCAL_INDEX_MESSAGE_UUID_NAME = "Message_UUID_Index"; @VisibleForTesting
private static final String LOCAL_INDEX_MESSAGE_UUID_KEY_SORT = "U"; 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_TTL = "E";
private static final String KEY_ENVELOPE_BYTES = "EB"; private static final String KEY_ENVELOPE_BYTES = "EB";

View File

@ -34,78 +34,28 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
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 AccountsManagerChangeNumberIntegrationTest { class AccountsManagerChangeNumberIntegrationTest {
private static final String ACCOUNTS_TABLE_NAME = "accounts_test";
private static final String NUMBERS_TABLE_NAME = "numbers_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 PNI_ASSIGNMENT_TABLE_NAME = "pni_assignment_test";
private static final String USERNAMES_TABLE_NAME = "usernames_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; private static final int SCAN_PAGE_SIZE = 1;
@RegisterExtension @RegisterExtension
static DynamoDbExtension ACCOUNTS_DYNAMO_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(ACCOUNTS_TABLE_NAME) Tables.ACCOUNTS,
.hashKey(Accounts.KEY_ACCOUNT_UUID) Tables.DELETED_ACCOUNTS,
.attributeDefinition(AttributeDefinition.builder() Tables.DELETED_ACCOUNTS_LOCK,
.attributeName(Accounts.KEY_ACCOUNT_UUID) Tables.NUMBERS,
.attributeType(ScalarAttributeType.B) Tables.PNI,
.build()) Tables.PNI_ASSIGNMENTS,
.build(); Tables.USERNAMES);
@RegisterExtension @RegisterExtension
static DynamoDbExtension DELETED_ACCOUNTS_DYNAMO_EXTENSION = DynamoDbExtension.builder() static final RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
.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();
private ClientPresenceManager clientPresenceManager; private ClientPresenceManager clientPresenceManager;
private DeletedAccounts deletedAccounts; private DeletedAccounts deletedAccounts;
@ -115,40 +65,6 @@ class AccountsManagerChangeNumberIntegrationTest {
@BeforeEach @BeforeEach
void setup() throws InterruptedException { 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<DynamicConfiguration> dynamicConfigurationManager = @SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
mock(DynamicConfigurationManager.class); mock(DynamicConfigurationManager.class);
@ -157,21 +73,21 @@ class AccountsManagerChangeNumberIntegrationTest {
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
final Accounts accounts = new Accounts( final Accounts accounts = new Accounts(
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), DYNAMO_DB_EXTENSION.getDynamoDbClient(),
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
ACCOUNTS_DYNAMO_EXTENSION.getTableName(), Tables.ACCOUNTS.tableName(),
NUMBERS_TABLE_NAME, Tables.NUMBERS.tableName(),
PNI_ASSIGNMENT_TABLE_NAME, Tables.PNI_ASSIGNMENTS.tableName(),
USERNAMES_TABLE_NAME, Tables.USERNAMES.tableName(),
SCAN_PAGE_SIZE); SCAN_PAGE_SIZE);
deletedAccounts = new DeletedAccounts(DELETED_ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DELETED_ACCOUNTS_DYNAMO_EXTENSION.getTableName(), Tables.DELETED_ACCOUNTS.tableName(),
NEEDS_RECONCILIATION_INDEX_NAME); Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
final DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, final DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
DELETED_ACCOUNTS_LOCK_DYNAMO_EXTENSION.getLegacyDynamoClient(), DYNAMO_DB_EXTENSION.getLegacyDynamoClient(),
DELETED_ACCOUNTS_LOCK_DYNAMO_EXTENSION.getTableName()); Tables.DELETED_ACCOUNTS_LOCK.tableName());
final SecureStorageClient secureStorageClient = mock(SecureStorageClient.class); final SecureStorageClient secureStorageClient = mock(SecureStorageClient.class);
when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null)); when(secureStorageClient.deleteStoredData(any())).thenReturn(CompletableFuture.completedFuture(null));
@ -185,7 +101,7 @@ class AccountsManagerChangeNumberIntegrationTest {
clientPresenceManager = mock(ClientPresenceManager.class); clientPresenceManager = mock(ClientPresenceManager.class);
final PhoneNumberIdentifiers phoneNumberIdentifiers = final PhoneNumberIdentifiers phoneNumberIdentifiers =
new PhoneNumberIdentifiers(PNI_DYNAMO_EXTENSION.getDynamoDbClient(), PNI_TABLE_NAME); new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.PNI.tableName());
accountsManager = new AccountsManager( accountsManager = new AccountsManager(
accounts, accounts,

View File

@ -48,34 +48,23 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; 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.DevicesHelper;
import org.whispersystems.textsecuregcm.tests.util.JsonHelpers; import org.whispersystems.textsecuregcm.tests.util.JsonHelpers;
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper; import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
import org.whispersystems.textsecuregcm.util.Pair; 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 { 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; private static final int SCAN_PAGE_SIZE = 1;
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(ACCOUNTS_TABLE_NAME) Tables.ACCOUNTS,
.hashKey(Accounts.KEY_ACCOUNT_UUID) Tables.NUMBERS,
.attributeDefinition(AttributeDefinition.builder() Tables.PNI_ASSIGNMENTS
.attributeName(Accounts.KEY_ACCOUNT_UUID) );
.attributeType(ScalarAttributeType.B)
.build())
.build();
private Accounts accounts; private Accounts accounts;
@ -88,51 +77,17 @@ class AccountsManagerConcurrentModificationIntegrationTest {
@BeforeEach @BeforeEach
void setup() throws InterruptedException { 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<DynamicConfiguration> dynamicConfigurationManager = @SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
mock(DynamicConfigurationManager.class); mock(DynamicConfigurationManager.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration()); when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
accounts = new Accounts( accounts = new Accounts(
dynamoDbExtension.getDynamoDbClient(), DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
dynamoDbExtension.getTableName(), Tables.ACCOUNTS.tableName(),
NUMBERS_TABLE_NAME, Tables.NUMBERS.tableName(),
PNI_TABLE_NAME, Tables.PNI_ASSIGNMENTS.tableName(),
USERNAMES_TABLE_NAME, Tables.USERNAMES.tableName(),
SCAN_PAGE_SIZE); SCAN_PAGE_SIZE);
{ {

View File

@ -45,23 +45,14 @@ import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient; import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client; import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue; import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.util.AttributeValues; 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.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.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest; import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
class AccountsManagerUsernameIntegrationTest { 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_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc"; private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc";
private static final int SCAN_PAGE_SIZE = 1; 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); private static final byte[] USERNAME_HASH_2 = Base64.getUrlDecoder().decode(BASE_64_URL_USERNAME_HASH_2);
@RegisterExtension @RegisterExtension
static DynamoDbExtension ACCOUNTS_DYNAMO_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(ACCOUNTS_TABLE_NAME) Tables.ACCOUNTS,
.hashKey(Accounts.KEY_ACCOUNT_UUID) Tables.NUMBERS,
.attributeDefinition(AttributeDefinition.builder() Tables.USERNAMES,
.attributeName(Accounts.KEY_ACCOUNT_UUID) Tables.PNI,
.attributeType(ScalarAttributeType.B) Tables.PNI_ASSIGNMENTS);
.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 @RegisterExtension
static RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static RedisClusterExtension CACHE_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@ -96,48 +75,6 @@ class AccountsManagerUsernameIntegrationTest {
@BeforeEach @BeforeEach
void setup() throws InterruptedException { 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); buildAccountsManager(1, 2, 10);
} }
@ -150,12 +87,12 @@ class AccountsManagerUsernameIntegrationTest {
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration); when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
accounts = Mockito.spy(new Accounts( accounts = Mockito.spy(new Accounts(
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient(), DYNAMO_DB_EXTENSION.getDynamoDbClient(),
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
ACCOUNTS_DYNAMO_EXTENSION.getTableName(), Tables.ACCOUNTS.tableName(),
NUMBERS_TABLE_NAME, Tables.NUMBERS.tableName(),
PNI_ASSIGNMENT_TABLE_NAME, Tables.PNI_ASSIGNMENTS.tableName(),
USERNAMES_TABLE_NAME, Tables.USERNAMES.tableName(),
SCAN_PAGE_SIZE)); SCAN_PAGE_SIZE));
final DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class); final DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
@ -167,7 +104,7 @@ class AccountsManagerUsernameIntegrationTest {
}).when(deletedAccountsManager).lockAndTake(any(), any()); }).when(deletedAccountsManager).lockAndTake(any(), any());
final PhoneNumberIdentifiers phoneNumberIdentifiers = 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); final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME))) 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())); AttributeValues.fromLong(Instant.now().plus(Duration.ofMinutes(1)).getEpochSecond()));
} }
i++; i++;
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
.tableName(USERNAMES_TABLE_NAME) .tableName(Tables.USERNAMES.tableName())
.item(item) .item(item)
.build()); .build());
} }
@ -222,8 +159,8 @@ class AccountsManagerUsernameIntegrationTest {
new ArrayList<>()); new ArrayList<>());
ArrayList<byte[]> usernameHashes = new ArrayList<>(Arrays.asList(USERNAME_HASH_1, USERNAME_HASH_2)); ArrayList<byte[]> usernameHashes = new ArrayList<>(Arrays.asList(USERNAME_HASH_1, USERNAME_HASH_2));
for (byte[] hash : usernameHashes) { for (byte[] hash : usernameHashes) {
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder() DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
.tableName(USERNAMES_TABLE_NAME) .tableName(Tables.USERNAMES.tableName())
.item(Map.of( .item(Map.of(
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()), Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()),
Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(hash))) Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(hash)))
@ -287,8 +224,8 @@ class AccountsManagerUsernameIntegrationTest {
long past = Instant.now().minus(Duration.ofMinutes(1)).getEpochSecond(); long past = Instant.now().minus(Duration.ofMinutes(1)).getEpochSecond();
// force expiration // force expiration
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().updateItem(UpdateItemRequest.builder() DYNAMO_DB_EXTENSION.getDynamoDbClient().updateItem(UpdateItemRequest.builder()
.tableName(USERNAMES_TABLE_NAME) .tableName(Tables.USERNAMES.tableName())
.key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1)))
.updateExpression("SET #ttl = :ttl") .updateExpression("SET #ttl = :ttl")
.expressionAttributeNames(Map.of("#ttl", Accounts.ATTR_TTL)) .expressionAttributeNames(Map.of("#ttl", Accounts.ATTR_TTL))

View File

@ -38,6 +38,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; 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.AccountsHelper;
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper; import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
import org.whispersystems.textsecuregcm.util.AttributeValues; import org.whispersystems.textsecuregcm.util.AttributeValues;
@ -45,18 +46,13 @@ import org.whispersystems.textsecuregcm.util.SystemMapper;
import org.whispersystems.textsecuregcm.util.TestClock; import org.whispersystems.textsecuregcm.util.TestClock;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient; import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient; 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.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException; 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.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; 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.Put;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ReturnValuesOnConditionCheckFailure; 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.TransactWriteItem;
import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest; import software.amazon.awssdk.services.dynamodb.model.TransactWriteItemsRequest;
import software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException; import software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException;
@ -65,10 +61,6 @@ import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
class AccountsTest { 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_1 = "9p6Tip7BFefFOJzv4kv4GyXEYsBVfk_WbjNejdlOvQE";
private static final String BASE_64_URL_USERNAME_HASH_2 = "NLUom-CHwtemcdvOTTXdmXmzRIV7F05leS8lwkVK_vc"; 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); 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; private static final int SCAN_PAGE_SIZE = 1;
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(ACCOUNTS_TABLE_NAME) Tables.ACCOUNTS,
.hashKey(Accounts.KEY_ACCOUNT_UUID) Tables.NUMBERS,
.attributeDefinition(AttributeDefinition.builder() Tables.PNI_ASSIGNMENTS,
.attributeName(Accounts.KEY_ACCOUNT_UUID) Tables.USERNAMES);
.attributeType(ScalarAttributeType.B)
.build())
.build();
private final TestClock clock = TestClock.pinned(Instant.EPOCH); private final TestClock clock = TestClock.pinned(Instant.EPOCH);
private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager; private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager;
@ -94,50 +82,6 @@ class AccountsTest {
@BeforeEach @BeforeEach
void setupAccountsDao() { 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<DynamicConfiguration> m = mock(DynamicConfigurationManager.class); @SuppressWarnings("unchecked") DynamicConfigurationManager<DynamicConfiguration> m = mock(DynamicConfigurationManager.class);
mockDynamicConfigManager = m; mockDynamicConfigManager = m;
@ -147,12 +91,12 @@ class AccountsTest {
this.accounts = new Accounts( this.accounts = new Accounts(
clock, clock,
dynamoDbExtension.getDynamoDbClient(), DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
dynamoDbExtension.getTableName(), Tables.ACCOUNTS.tableName(),
NUMBER_CONSTRAINT_TABLE_NAME, Tables.NUMBERS.tableName(),
PNI_CONSTRAINT_TABLE_NAME, Tables.PNI_ASSIGNMENTS.tableName(),
USERNAME_CONSTRAINT_TABLE_NAME, Tables.USERNAMES.tableName(),
SCAN_PAGE_SIZE); SCAN_PAGE_SIZE);
} }
@ -247,7 +191,7 @@ class AccountsTest {
final TransactWriteItem phoneNumberConstraintPut = TransactWriteItem.builder() final TransactWriteItem phoneNumberConstraintPut = TransactWriteItem.builder()
.put( .put(
Put.builder() Put.builder()
.tableName(NUMBER_CONSTRAINT_TABLE_NAME) .tableName(Tables.NUMBERS.tableName())
.item(Map.of( .item(Map.of(
Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(account.getNumber()), Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(account.getNumber()),
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(account.getUuid()))) Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(account.getUuid())))
@ -264,7 +208,7 @@ class AccountsTest {
final TransactWriteItem accountPut = TransactWriteItem.builder() final TransactWriteItem accountPut = TransactWriteItem.builder()
.put(Put.builder() .put(Put.builder()
.tableName(ACCOUNTS_TABLE_NAME) .tableName(Tables.ACCOUNTS.tableName())
.conditionExpression("attribute_not_exists(#number) OR #number = :number") .conditionExpression("attribute_not_exists(#number) OR #number = :number")
.expressionAttributeNames(Map.of("#number", Accounts.ATTR_ACCOUNT_E164)) .expressionAttributeNames(Map.of("#number", Accounts.ATTR_ACCOUNT_E164))
.expressionAttributeValues(Map.of(":number", AttributeValues.fromString(account.getNumber()))) .expressionAttributeValues(Map.of(":number", AttributeValues.fromString(account.getNumber())))
@ -277,7 +221,7 @@ class AccountsTest {
.build()) .build())
.build(); .build();
dynamoDbExtension.getDynamoDbClient().transactWriteItems(TransactWriteItemsRequest.builder() DYNAMO_DB_EXTENSION.getDynamoDbClient().transactWriteItems(TransactWriteItemsRequest.builder()
.transactItems(phoneNumberConstraintPut, accountPut) .transactItems(phoneNumberConstraintPut, accountPut)
.build()); .build());
} }
@ -389,8 +333,8 @@ class AccountsTest {
final DynamoDbAsyncClient dynamoDbAsyncClient = mock(DynamoDbAsyncClient.class); final DynamoDbAsyncClient dynamoDbAsyncClient = mock(DynamoDbAsyncClient.class);
accounts = new Accounts(mock(DynamoDbClient.class), accounts = new Accounts(mock(DynamoDbClient.class),
dynamoDbAsyncClient, dynamoDbExtension.getTableName(), dynamoDbAsyncClient, Tables.ACCOUNTS.tableName(),
NUMBER_CONSTRAINT_TABLE_NAME, PNI_CONSTRAINT_TABLE_NAME, USERNAME_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE); Tables.NUMBERS.tableName(), Tables.PNI_ASSIGNMENTS.tableName(), Tables.USERNAMES.tableName(), SCAN_PAGE_SIZE);
Exception e = TransactionConflictException.builder().build(); Exception e = TransactionConflictException.builder().build();
e = wrapException ? new CompletionException(e) : e; e = wrapException ? new CompletionException(e) : e;
@ -612,8 +556,8 @@ class AccountsTest {
final UUID existingPhoneNumberIdentifier = UUID.randomUUID(); final UUID existingPhoneNumberIdentifier = UUID.randomUUID();
// Artificially inject a conflicting PNI entry // Artificially inject a conflicting PNI entry
dynamoDbExtension.getDynamoDbClient().putItem(PutItemRequest.builder() DYNAMO_DB_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
.tableName(PNI_CONSTRAINT_TABLE_NAME) .tableName(Tables.PNI_ASSIGNMENTS.tableName())
.item(Map.of( .item(Map.of(
Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(existingPhoneNumberIdentifier), Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(existingPhoneNumberIdentifier),
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(existingAccountIdentifier))) Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(existingAccountIdentifier)))
@ -649,9 +593,9 @@ class AccountsTest {
accounts.confirmUsernameHash(account, USERNAME_HASH_2); accounts.confirmUsernameHash(account, USERNAME_HASH_2);
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty(); assertThat(accounts.getByUsernameHash(USERNAME_HASH_1)).isEmpty();
assertThat(dynamoDbExtension.getDynamoDbClient() assertThat(DYNAMO_DB_EXTENSION.getDynamoDbClient()
.getItem(GetItemRequest.builder() .getItem(GetItemRequest.builder()
.tableName(USERNAME_CONSTRAINT_TABLE_NAME) .tableName(Tables.USERNAMES.tableName())
.key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1)))
.build()) .build())
.item()).isEmpty(); .item()).isEmpty();
@ -775,9 +719,9 @@ class AccountsTest {
assertArrayEquals(account1.getUsernameHash().orElseThrow(), USERNAME_HASH_1); assertArrayEquals(account1.getUsernameHash().orElseThrow(), USERNAME_HASH_1);
assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account1.getUuid()); assertThat(accounts.getByUsernameHash(USERNAME_HASH_1).get().getUuid()).isEqualTo(account1.getUuid());
final Map<String, AttributeValue> usernameConstraintRecord = dynamoDbExtension.getDynamoDbClient() final Map<String, AttributeValue> usernameConstraintRecord = DYNAMO_DB_EXTENSION.getDynamoDbClient()
.getItem(GetItemRequest.builder() .getItem(GetItemRequest.builder()
.tableName(USERNAME_CONSTRAINT_TABLE_NAME) .tableName(Tables.USERNAMES.tableName())
.key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1))) .key(Map.of(Accounts.ATTR_USERNAME_HASH, AttributeValues.fromByteArray(USERNAME_HASH_1)))
.build()) .build())
.item(); .item();
@ -892,9 +836,9 @@ class AccountsTest {
} }
private void assertPhoneNumberConstraintExists(final String number, final UUID uuid) { 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() GetItemRequest.builder()
.tableName(NUMBER_CONSTRAINT_TABLE_NAME) .tableName(Tables.NUMBERS.tableName())
.key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number))) .key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number)))
.build()); .build());
@ -903,9 +847,9 @@ class AccountsTest {
} }
private void assertPhoneNumberConstraintDoesNotExist(final String number) { private void assertPhoneNumberConstraintDoesNotExist(final String number) {
final GetItemResponse numberConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( final GetItemResponse numberConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem(
GetItemRequest.builder() GetItemRequest.builder()
.tableName(NUMBER_CONSTRAINT_TABLE_NAME) .tableName(Tables.NUMBERS.tableName())
.key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number))) .key(Map.of(Accounts.ATTR_ACCOUNT_E164, AttributeValues.fromString(number)))
.build()); .build());
@ -913,9 +857,9 @@ class AccountsTest {
} }
private void assertPhoneNumberIdentifierConstraintExists(final UUID phoneNumberIdentifier, final UUID uuid) { 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() GetItemRequest.builder()
.tableName(PNI_CONSTRAINT_TABLE_NAME) .tableName(Tables.PNI_ASSIGNMENTS.tableName())
.key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier))) .key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier)))
.build()); .build());
@ -924,9 +868,9 @@ class AccountsTest {
} }
private void assertPhoneNumberIdentifierConstraintDoesNotExist(final UUID phoneNumberIdentifier) { private void assertPhoneNumberIdentifierConstraintDoesNotExist(final UUID phoneNumberIdentifier) {
final GetItemResponse pniConstraintResponse = dynamoDbExtension.getDynamoDbClient().getItem( final GetItemResponse pniConstraintResponse = DYNAMO_DB_EXTENSION.getDynamoDbClient().getItem(
GetItemRequest.builder() GetItemRequest.builder()
.tableName(PNI_CONSTRAINT_TABLE_NAME) .tableName(Tables.PNI_ASSIGNMENTS.tableName())
.key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier))) .key(Map.of(Accounts.ATTR_PNI_UUID, AttributeValues.fromUUID(phoneNumberIdentifier)))
.build()); .build());
@ -934,10 +878,10 @@ class AccountsTest {
} }
private void verifyStoredState(String number, UUID uuid, UUID pni, byte[] usernameHash, Account expecting, boolean canonicallyDiscoverable) { 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() final GetItemResponse get = db.getItem(GetItemRequest.builder()
.tableName(dynamoDbExtension.getTableName()) .tableName(Tables.ACCOUNTS.tableName())
.key(Map.of(Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(uuid))) .key(Map.of(Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(uuid)))
.consistentRead(true) .consistentRead(true)
.build()); .build());

View File

@ -19,75 +19,27 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.api.function.Executable;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes;
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
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 DeletedAccountsManagerTest { class DeletedAccountsManagerTest {
private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test";
@RegisterExtension @RegisterExtension
static final DynamoDbExtension DELETED_ACCOUNTS_DYNAMODB_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION =
.tableName("deleted_accounts_test") new DynamoDbExtension(Tables.DELETED_ACCOUNTS, Tables.DELETED_ACCOUNTS_LOCK);
.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();
private DeletedAccounts deletedAccounts; private DeletedAccounts deletedAccounts;
private DeletedAccountsManager deletedAccountsManager; private DeletedAccountsManager deletedAccountsManager;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
deletedAccounts = new DeletedAccounts(DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getDynamoDbClient(), deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DELETED_ACCOUNTS_DYNAMODB_EXTENSION.getTableName(), Tables.DELETED_ACCOUNTS.tableName(),
NEEDS_RECONCILIATION_INDEX_NAME); Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
deletedAccountsManager = new DeletedAccountsManager(deletedAccounts, deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getLegacyDynamoClient(), DYNAMO_DB_EXTENSION.getLegacyDynamoClient(),
DELETED_ACCOUNTS_LOCK_DYNAMODB_EXTENSION.getTableName()); Tables.DELETED_ACCOUNTS_LOCK.tableName());
} }
@Test @Test

View File

@ -16,62 +16,22 @@ import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; 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 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 { class DeletedAccountsTest {
private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.DELETED_ACCOUNTS);
.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();
private DeletedAccounts deletedAccounts; private DeletedAccounts deletedAccounts;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
deletedAccounts = new DeletedAccounts(dynamoDbExtension.getDynamoDbClient(), deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getTableName(), Tables.DELETED_ACCOUNTS.tableName(),
NEEDS_RECONCILIATION_INDEX_NAME); Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
} }
@Test @Test

View File

@ -1,3 +1,8 @@
/*
* Copyright 2021-2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.storage; package org.whispersystems.textsecuregcm.storage;
import com.almworks.sqlite4java.SQLite; import com.almworks.sqlite4java.SQLite;
@ -31,7 +36,23 @@ import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback { 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<AttributeDefinition> attributeDefinitions();
public List<GlobalSecondaryIndex> globalSecondaryIndexes();
public List<LocalSecondaryIndex> localSecondaryIndexes();
}
record RawSchema(
String tableName,
String hashKeyName,
String rangeKeyName,
List<AttributeDefinition> attributeDefinitions,
List<GlobalSecondaryIndex> globalSecondaryIndexes,
List<LocalSecondaryIndex> localSecondaryIndexes
) implements TableSchema { }
static final ProvisionedThroughput DEFAULT_PROVISIONED_THROUGHPUT = ProvisionedThroughput.builder() static final ProvisionedThroughput DEFAULT_PROVISIONED_THROUGHPUT = ProvisionedThroughput.builder()
.readCapacityUnits(20L) .readCapacityUnits(20L)
@ -42,42 +63,14 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
private DynamoDBProxyServer server; private DynamoDBProxyServer server;
private int port; private int port;
private final String tableName; private final List<TableSchema> schemas;
private final String hashKeyName;
private final String rangeKeyName;
private final List<AttributeDefinition> attributeDefinitions;
private final List<GlobalSecondaryIndex> globalSecondaryIndexes;
private final List<LocalSecondaryIndex> localSecondaryIndexes;
private final long readCapacityUnits;
private final long writeCapacityUnits;
private DynamoDbClient dynamoDB2; private DynamoDbClient dynamoDB2;
private DynamoDbAsyncClient dynamoAsyncDB2; private DynamoDbAsyncClient dynamoAsyncDB2;
private AmazonDynamoDB legacyDynamoClient; private AmazonDynamoDB legacyDynamoClient;
private DynamoDbExtension(String tableName, String hashKey, String rangeKey, public DynamoDbExtension(TableSchema... schemas) {
List<AttributeDefinition> attributeDefinitions, List<GlobalSecondaryIndex> globalSecondaryIndexes, this.schemas = List.of(schemas);
final List<LocalSecondaryIndex> 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();
} }
private static void loadLibrary() { private static void loadLibrary() {
@ -114,32 +107,33 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
initializeClient(); initializeClient();
createTable(); createTables();
} }
private void createTable() { private void createTables() {
schemas.stream().forEach(this::createTable);
}
private void createTable(TableSchema schema) {
KeySchemaElement[] keySchemaElements; KeySchemaElement[] keySchemaElements;
if (rangeKeyName == null) { if (schema.rangeKeyName() == null) {
keySchemaElements = new KeySchemaElement[] { keySchemaElements = new KeySchemaElement[] {
KeySchemaElement.builder().attributeName(hashKeyName).keyType(KeyType.HASH).build(), KeySchemaElement.builder().attributeName(schema.hashKeyName()).keyType(KeyType.HASH).build(),
}; };
} else { } else {
keySchemaElements = new KeySchemaElement[] { keySchemaElements = new KeySchemaElement[] {
KeySchemaElement.builder().attributeName(hashKeyName).keyType(KeyType.HASH).build(), KeySchemaElement.builder().attributeName(schema.hashKeyName()).keyType(KeyType.HASH).build(),
KeySchemaElement.builder().attributeName(rangeKeyName).keyType(KeyType.RANGE).build(), KeySchemaElement.builder().attributeName(schema.rangeKeyName()).keyType(KeyType.RANGE).build(),
}; };
} }
final CreateTableRequest createTableRequest = CreateTableRequest.builder() final CreateTableRequest createTableRequest = CreateTableRequest.builder()
.tableName(tableName) .tableName(schema.tableName())
.keySchema(keySchemaElements) .keySchema(keySchemaElements)
.attributeDefinitions(attributeDefinitions.isEmpty() ? null : attributeDefinitions) .attributeDefinitions(schema.attributeDefinitions().isEmpty() ? null : schema.attributeDefinitions())
.globalSecondaryIndexes(globalSecondaryIndexes.isEmpty() ? null : globalSecondaryIndexes) .globalSecondaryIndexes(schema.globalSecondaryIndexes().isEmpty() ? null : schema.globalSecondaryIndexes())
.localSecondaryIndexes(localSecondaryIndexes.isEmpty() ? null : localSecondaryIndexes) .localSecondaryIndexes(schema.localSecondaryIndexes().isEmpty() ? null : schema.localSecondaryIndexes())
.provisionedThroughput(ProvisionedThroughput.builder() .provisionedThroughput(DEFAULT_PROVISIONED_THROUGHPUT)
.readCapacityUnits(readCapacityUnits)
.writeCapacityUnits(writeCapacityUnits)
.build())
.build(); .build();
getDynamoDbClient().createTable(createTableRequest); getDynamoDbClient().createTable(createTableRequest);
@ -178,60 +172,6 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
.build(); .build();
} }
public static class DynamoDbExtensionBuilder {
private String tableName = DEFAULT_TABLE_NAME;
private String hashKey;
private String rangeKey;
private final List<AttributeDefinition> attributeDefinitions = new ArrayList<>();
private final List<GlobalSecondaryIndex> globalSecondaryIndexes = new ArrayList<>();
private final List<LocalSecondaryIndex> 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() { public DynamoDbClient getDynamoDbClient() {
return dynamoDB2; return dynamoDB2;
} }
@ -243,8 +183,4 @@ public class DynamoDbExtension implements BeforeEachCallback, AfterEachCallback
public AmazonDynamoDB getLegacyDynamoClient() { public AmazonDynamoDB getLegacyDynamoClient() {
return legacyDynamoClient; return legacyDynamoClient;
} }
public String getTableName() {
return tableName;
}
} }

View File

@ -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<AttributeDefinition> attributeDefinitions;
private final List<GlobalSecondaryIndex> globalSecondaryIndexes;
private final List<LocalSecondaryIndex> localSecondaryIndexes;
Tables(
final String tableName,
final String hashKeyName,
final String rangeKeyName,
final List<AttributeDefinition> attributeDefinitions,
final List<GlobalSecondaryIndex> globalSecondaryIndexes,
final List<LocalSecondaryIndex> 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<AttributeDefinition> attributeDefinitions() {
return attributeDefinitions;
}
public List<GlobalSecondaryIndex> globalSecondaryIndexes() {
return globalSecondaryIndexes;
}
public List<LocalSecondaryIndex> localSecondaryIndexes() {
return localSecondaryIndexes;
}
}
}

View File

@ -20,24 +20,15 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest; import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialRequest;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class IssuedReceiptsManagerTest { class IssuedReceiptsManagerTest {
private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; 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(); private static final SecureRandom SECURE_RANDOM = new SecureRandom();
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.ISSUED_RECEIPTS);
.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();
ReceiptCredentialRequest receiptCredentialRequest; ReceiptCredentialRequest receiptCredentialRequest;
IssuedReceiptsManager issuedReceiptsManager; IssuedReceiptsManager issuedReceiptsManager;
@ -48,9 +39,9 @@ class IssuedReceiptsManagerTest {
byte[] generator = new byte[16]; byte[] generator = new byte[16];
SECURE_RANDOM.nextBytes(generator); SECURE_RANDOM.nextBytes(generator);
issuedReceiptsManager = new IssuedReceiptsManager( issuedReceiptsManager = new IssuedReceiptsManager(
ISSUED_RECEIPTS_TABLE_NAME, Tables.ISSUED_RECEIPTS.tableName(),
Duration.ofDays(90), Duration.ofDays(90),
dynamoDbExtension.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
generator); generator);
} }

View File

@ -15,38 +15,22 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.entities.PreKey; 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.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class KeysTest { class KeysTest {
private static final String TABLE_NAME = "Signal_Keys_Test";
private Keys keys; private Keys keys;
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.KEYS);
.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();
private static final UUID ACCOUNT_UUID = UUID.randomUUID(); private static final UUID ACCOUNT_UUID = UUID.randomUUID();
private static final long DEVICE_ID = 1L; private static final long DEVICE_ID = 1L;
@BeforeEach @BeforeEach
void setup() { void setup() {
keys = new Keys(dynamoDbExtension.getDynamoDbClient(), TABLE_NAME); keys = new Keys(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.KEYS.tableName());
} }
@Test @Test

View File

@ -35,7 +35,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration; import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.entities.MessageProtos; import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension; 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.Scheduler;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@ -44,7 +44,7 @@ import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
class MessagePersisterIntegrationTest { class MessagePersisterIntegrationTest {
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES);
@RegisterExtension @RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@ -74,8 +74,8 @@ class MessagePersisterIntegrationTest {
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messageDeletionExecutorService = Executors.newSingleThreadExecutor(); messageDeletionExecutorService = Executors.newSingleThreadExecutor();
final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), final MessagesDynamoDb messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(14), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14),
messageDeletionExecutorService); messageDeletionExecutorService);
final AccountsManager accountsManager = mock(AccountsManager.class); final AccountsManager accountsManager = mock(AccountsManager.class);
@ -164,10 +164,10 @@ class MessagePersisterIntegrationTest {
messagePersister.stop(); messagePersister.stop();
DynamoDbClient dynamoDB = dynamoDbExtension.getDynamoDbClient(); DynamoDbClient dynamoDB = DYNAMO_DB_EXTENSION.getDynamoDbClient();
final List<MessageProtos.Envelope> persistedMessages = final List<MessageProtos.Envelope> 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 -> { .map(item -> {
try { try {
return MessagesDynamoDb.convertItemToEnvelope(item); return MessagesDynamoDb.convertItemToEnvelope(item);

View File

@ -26,8 +26,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import org.whispersystems.textsecuregcm.entities.MessageProtos; 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.MessageHelper;
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
@ -77,13 +77,13 @@ class MessagesDynamoDbTest {
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES);
@BeforeEach @BeforeEach
void setup() { void setup() {
messageDeletionExecutorService = Executors.newSingleThreadExecutor(); messageDeletionExecutorService = Executors.newSingleThreadExecutor();
messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(14), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(14),
messageDeletionExecutorService); messageDeletionExecutorService);
} }
@ -170,7 +170,7 @@ class MessagesDynamoDbTest {
.thenRequest(halfOfMessageLoadLimit) .thenRequest(halfOfMessageLoadLimit)
.expectNextCount(halfOfMessageLoadLimit) .expectNextCount(halfOfMessageLoadLimit)
// the first 100 should be fetched and buffered, but further requests should fail // the first 100 should be fetched and buffered, but further requests should fail
.then(() -> dynamoDbExtension.stopServer()) .then(() -> DYNAMO_DB_EXTENSION.stopServer())
.thenRequest(halfOfMessageLoadLimit) .thenRequest(halfOfMessageLoadLimit)
.expectNextCount(halfOfMessageLoadLimit) .expectNextCount(halfOfMessageLoadLimit)
// weve consumed all the buffered messages, so a single request will fail // weve consumed all the buffered messages, so a single request will fail

View File

@ -14,48 +14,19 @@ import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
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 PhoneNumberIdentifiersTest { class PhoneNumberIdentifiersTest {
private static final String PNI_TABLE_NAME = "pni_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() static DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PNI);
.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();
private PhoneNumberIdentifiers phoneNumberIdentifiers; private PhoneNumberIdentifiers phoneNumberIdentifiers;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
phoneNumberIdentifiers = new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(), PNI_TABLE_NAME); phoneNumberIdentifiers = new PhoneNumberIdentifiers(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
Tables.PNI.tableName());
} }
@Test @Test

View File

@ -12,9 +12,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.textsecuregcm.util.AttributeValues; 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.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -27,30 +26,16 @@ import java.util.stream.Stream;
public abstract class ProfilesTest { public abstract class ProfilesTest {
private static final String PROFILES_TABLE_NAME = "profiles_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PROFILES);
.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();
private Profiles profiles; private Profiles profiles;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
profiles = new Profiles(dynamoDbExtension.getDynamoDbClient(), profiles = new Profiles(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
PROFILES_TABLE_NAME); Tables.PROFILES.tableName());
} }
@Test @Test

View File

@ -18,8 +18,7 @@ import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class PushChallengeDynamoDbTest { class PushChallengeDynamoDbTest {
@ -28,22 +27,16 @@ class PushChallengeDynamoDbTest {
private static final long CURRENT_TIME_MILLIS = 1_000_000_000; private static final long CURRENT_TIME_MILLIS = 1_000_000_000;
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final String TABLE_NAME = "push_challenge_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.PUSH_CHALLENGES);
.tableName(TABLE_NAME)
.hashKey(PushChallengeDynamoDb.KEY_ACCOUNT_UUID)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(PushChallengeDynamoDb.KEY_ACCOUNT_UUID)
.attributeType(ScalarAttributeType.B)
.build())
.build();
@BeforeEach @BeforeEach
void setUp() { void setUp() {
this.pushChallengeDynamoDb = new PushChallengeDynamoDb(dynamoDbExtension.getDynamoDbClient(), TABLE_NAME, Clock.fixed( this.pushChallengeDynamoDb = new PushChallengeDynamoDb(
Instant.ofEpochMilli(CURRENT_TIME_MILLIS), ZoneId.systemDefault())); DYNAMO_DB_EXTENSION.getDynamoDbClient(),
Tables.PUSH_CHALLENGES.tableName(),
Clock.fixed(Instant.ofEpochMilli(CURRENT_TIME_MILLIS), ZoneId.systemDefault()));
} }
@Test @Test

View File

@ -21,17 +21,14 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.auth.SaltedTokenHash; import org.whispersystems.textsecuregcm.auth.SaltedTokenHash;
import org.whispersystems.textsecuregcm.util.AttributeValues; import org.whispersystems.textsecuregcm.util.AttributeValues;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.util.MockUtils; import org.whispersystems.textsecuregcm.util.MockUtils;
import org.whispersystems.textsecuregcm.util.MutableClock; 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.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
public class RegistrationRecoveryTest { public class RegistrationRecoveryTest {
private static final String TABLE_NAME = "registration_recovery_passwords";
private static final MutableClock CLOCK = MockUtils.mutableClock(0); private static final MutableClock CLOCK = MockUtils.mutableClock(0);
private static final Duration EXPIRATION = Duration.ofSeconds(1000); private static final Duration EXPIRATION = Duration.ofSeconds(1000);
@ -43,14 +40,8 @@ public class RegistrationRecoveryTest {
private static final SaltedTokenHash ANOTHER_HASH = SaltedTokenHash.generateFor("pass2"); private static final SaltedTokenHash ANOTHER_HASH = SaltedTokenHash.generateFor("pass2");
@RegisterExtension @RegisterExtension
private static final DynamoDbExtension DB_EXTENSION = DynamoDbExtension.builder() private static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(TABLE_NAME) Tables.REGISTRATION_RECOVERY_PASSWORDS);
.hashKey(RegistrationRecoveryPasswords.KEY_E164)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(RegistrationRecoveryPasswords.KEY_E164)
.attributeType(ScalarAttributeType.S)
.build())
.build();
private RegistrationRecoveryPasswords store; private RegistrationRecoveryPasswords store;
@ -60,10 +51,10 @@ public class RegistrationRecoveryTest {
public void before() throws Exception { public void before() throws Exception {
CLOCK.setTimeMillis(Clock.systemUTC().millis()); CLOCK.setTimeMillis(Clock.systemUTC().millis());
store = new RegistrationRecoveryPasswords( store = new RegistrationRecoveryPasswords(
DB_EXTENSION.getTableName(), Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName(),
EXPIRATION, EXPIRATION,
DB_EXTENSION.getDynamoDbClient(), DYNAMO_DB_EXTENSION.getDynamoDbClient(),
DB_EXTENSION.getDynamoDbAsyncClient(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(),
CLOCK CLOCK
); );
manager = new RegistrationRecoveryPasswordsManager(store); manager = new RegistrationRecoveryPasswordsManager(store);
@ -147,8 +138,8 @@ public class RegistrationRecoveryTest {
} }
private static long fetchTimestamp(final String number) throws ExecutionException, InterruptedException { private static long fetchTimestamp(final String number) throws ExecutionException, InterruptedException {
return DB_EXTENSION.getDynamoDbAsyncClient().getItem(GetItemRequest.builder() return DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient().getItem(GetItemRequest.builder()
.tableName(DB_EXTENSION.getTableName()) .tableName(Tables.REGISTRATION_RECOVERY_PASSWORDS.tableName())
.key(Map.of(RegistrationRecoveryPasswords.KEY_E164, AttributeValues.fromString(number))) .key(Map.of(RegistrationRecoveryPasswords.KEY_E164, AttributeValues.fromString(number)))
.build()) .build())
.thenApply(getItemResponse -> { .thenApply(getItemResponse -> {

View File

@ -9,8 +9,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -18,23 +17,14 @@ import static org.assertj.core.api.Assertions.assertThat;
class RemoteConfigsTest { class RemoteConfigsTest {
private static final String REMOTE_CONFIGS_TABLE_NAME = "remote_configs_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REMOTE_CONFIGS);
.tableName(REMOTE_CONFIGS_TABLE_NAME)
.hashKey(RemoteConfigs.KEY_NAME)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(RemoteConfigs.KEY_NAME)
.attributeType(ScalarAttributeType.S)
.build())
.build();
private RemoteConfigs remoteConfigs; private RemoteConfigs remoteConfigs;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
remoteConfigs = new RemoteConfigs(dynamoDbExtension.getDynamoDbClient(), REMOTE_CONFIGS_TABLE_NAME); remoteConfigs = new RemoteConfigs(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.REMOTE_CONFIGS.tableName());
} }
@Test @Test

View File

@ -14,30 +14,23 @@ import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.util.UUIDUtil; import org.whispersystems.textsecuregcm.util.UUIDUtil;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class ReportMessageDynamoDbTest { class ReportMessageDynamoDbTest {
private ReportMessageDynamoDb reportMessageDynamoDb; private ReportMessageDynamoDb reportMessageDynamoDb;
private static final String TABLE_NAME = "report_message_test";
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REPORT_MESSAGES);
.tableName(TABLE_NAME)
.hashKey(ReportMessageDynamoDb.KEY_HASH)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(ReportMessageDynamoDb.KEY_HASH)
.attributeType(ScalarAttributeType.B)
.build())
.build();
@BeforeEach @BeforeEach
void setUp() { 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 @Test

View File

@ -14,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Clock; import java.time.Clock;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -39,14 +40,17 @@ class SerializedExpireableJsonDynamoStoreTest {
} }
@RegisterExtension @RegisterExtension
static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(
.tableName(TABLE_NAME) new DynamoDbExtension.RawSchema(
.hashKey(SerializedExpireableJsonDynamoStore.KEY_KEY) TABLE_NAME,
.attributeDefinition(AttributeDefinition.builder() SerializedExpireableJsonDynamoStore.KEY_KEY,
.attributeName(SerializedExpireableJsonDynamoStore.KEY_KEY) null,
.attributeType(ScalarAttributeType.S) List.of(AttributeDefinition.builder()
.build()) .attributeName(SerializedExpireableJsonDynamoStore.KEY_KEY)
.build(); .attributeType(ScalarAttributeType.S)
.build()),
List.of(),
List.of()));
private SerializedExpireableJsonDynamoStore<T> store; private SerializedExpireableJsonDynamoStore<T> store;

View File

@ -28,49 +28,16 @@ import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager.Record; import org.whispersystems.textsecuregcm.storage.SubscriptionManager.Record;
import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer; import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor; import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
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 SubscriptionManagerTest { class SubscriptionManagerTest {
private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; private static final long NOW_EPOCH_SECONDS = 1_500_000_000L;
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(3); 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(); private static final SecureRandom SECURE_RANDOM = new SecureRandom();
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder(). static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.SUBSCRIPTIONS);
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();
byte[] user; byte[] user;
byte[] password; byte[] password;
@ -85,7 +52,7 @@ class SubscriptionManagerTest {
customer = Base64.getEncoder().encodeToString(getRandomBytes(16)); customer = Base64.getEncoder().encodeToString(getRandomBytes(16));
created = Instant.ofEpochSecond(NOW_EPOCH_SECONDS); created = Instant.ofEpochSecond(NOW_EPOCH_SECONDS);
subscriptionManager = new SubscriptionManager( subscriptionManager = new SubscriptionManager(
SUBSCRIPTIONS_TABLE_NAME, dynamoDbExtension.getDynamoDbAsyncClient()); Tables.SUBSCRIPTIONS.tableName(), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient());
} }
@Test @Test

View File

@ -19,15 +19,12 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode; import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition; import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class VerificationCodeStoreTest { class VerificationCodeStoreTest {
private VerificationCodeStore verificationCodeStore; private VerificationCodeStore verificationCodeStore;
private static final String TABLE_NAME = "verification_code_test";
private static final String PHONE_NUMBER = "+14151112222"; private static final String PHONE_NUMBER = "+14151112222";
private static final long VALID_TIMESTAMP = Instant.now().toEpochMilli(); private static final long VALID_TIMESTAMP = Instant.now().toEpochMilli();
@ -35,18 +32,12 @@ class VerificationCodeStoreTest {
Duration.ofHours(1)).toEpochMilli(); Duration.ofHours(1)).toEpochMilli();
@RegisterExtension @RegisterExtension
static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.VERIFICATION_CODES);
.tableName(TABLE_NAME)
.hashKey(VerificationCodeStore.KEY_E164)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(VerificationCodeStore.KEY_E164)
.attributeType(ScalarAttributeType.S)
.build())
.build();
@BeforeEach @BeforeEach
void setUp() { void setUp() {
verificationCodeStore = new VerificationCodeStore(DYNAMO_DB_EXTENSION.getDynamoDbClient(), TABLE_NAME); verificationCodeStore = new VerificationCodeStore(
DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.VERIFICATION_CODES.tableName());
} }
@Test @Test

View File

@ -21,32 +21,23 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.registration.VerificationSession; import org.whispersystems.textsecuregcm.registration.VerificationSession;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.util.ExceptionUtils; 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.ConditionalCheckFailedException;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class VerificationSessionsTest { class VerificationSessionsTest {
private static final String TABLE_NAME = "verification_sessions_test";
private static final Clock clock = Clock.systemUTC(); private static final Clock clock = Clock.systemUTC();
@RegisterExtension @RegisterExtension
static final DynamoDbExtension DYNAMO_DB_EXTENSION = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.VERIFICATION_SESSIONS);
.tableName(TABLE_NAME)
.hashKey(VerificationSessions.KEY_KEY)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(VerificationSessions.KEY_KEY)
.attributeType(ScalarAttributeType.S)
.build())
.build();
private VerificationSessions verificationSessions; private VerificationSessions verificationSessions;
@BeforeEach @BeforeEach
void setUp() { 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 @Test

View File

@ -21,27 +21,18 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.signal.libsignal.zkgroup.InvalidInputException; import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.receipts.ReceiptSerial; import org.signal.libsignal.zkgroup.receipts.ReceiptSerial;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager; import org.whispersystems.textsecuregcm.storage.RedeemedReceiptsManager;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper; import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.TestClock; import org.whispersystems.textsecuregcm.util.TestClock;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
class RedeemedReceiptsManagerTest { class RedeemedReceiptsManagerTest {
private static final long NOW_EPOCH_SECONDS = 1_500_000_000L; 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(); private static final SecureRandom SECURE_RANDOM = new SecureRandom();
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder() static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.REDEEMED_RECEIPTS);
.tableName(REDEEMED_RECEIPTS_TABLE_NAME)
.hashKey(RedeemedReceiptsManager.KEY_SERIAL)
.attributeDefinition(AttributeDefinition.builder()
.attributeName(RedeemedReceiptsManager.KEY_SERIAL)
.attributeType(ScalarAttributeType.B)
.build())
.build();
Clock clock = TestClock.pinned(Instant.ofEpochSecond(NOW_EPOCH_SECONDS)); Clock clock = TestClock.pinned(Instant.ofEpochSecond(NOW_EPOCH_SECONDS));
ReceiptSerial receiptSerial; ReceiptSerial receiptSerial;
@ -53,7 +44,10 @@ class RedeemedReceiptsManagerTest {
SECURE_RANDOM.nextBytes(receiptSerialBytes); SECURE_RANDOM.nextBytes(receiptSerialBytes);
receiptSerial = new ReceiptSerial(receiptSerialBytes); receiptSerial = new ReceiptSerial(receiptSerialBytes);
redeemedReceiptsManager = new RedeemedReceiptsManager( 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 @Test

View File

@ -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();
}
}

View File

@ -51,11 +51,11 @@ import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtension; import org.whispersystems.textsecuregcm.storage.DynamoDbExtension;
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
import org.whispersystems.textsecuregcm.storage.MessagesCache; import org.whispersystems.textsecuregcm.storage.MessagesCache;
import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb; import org.whispersystems.textsecuregcm.storage.MessagesDynamoDb;
import org.whispersystems.textsecuregcm.storage.MessagesManager; import org.whispersystems.textsecuregcm.storage.MessagesManager;
import org.whispersystems.textsecuregcm.storage.ReportMessageManager; import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
import org.whispersystems.textsecuregcm.tests.util.MessagesDynamoDbExtension;
import org.whispersystems.textsecuregcm.util.Pair; import org.whispersystems.textsecuregcm.util.Pair;
import org.whispersystems.websocket.WebSocketClient; import org.whispersystems.websocket.WebSocketClient;
import org.whispersystems.websocket.messages.WebSocketResponseMessage; import org.whispersystems.websocket.messages.WebSocketResponseMessage;
@ -65,7 +65,7 @@ import reactor.core.scheduler.Schedulers;
class WebSocketConnectionIntegrationTest { class WebSocketConnectionIntegrationTest {
@RegisterExtension @RegisterExtension
static DynamoDbExtension dynamoDbExtension = MessagesDynamoDbExtension.build(); static final DynamoDbExtension DYNAMO_DB_EXTENSION = new DynamoDbExtension(Tables.MESSAGES);
@RegisterExtension @RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build(); static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@ -90,8 +90,8 @@ class WebSocketConnectionIntegrationTest {
messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery"); messageDeliveryScheduler = Schedulers.newBoundedElastic(10, 10_000, "messageDelivery");
messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(), messagesCache = new MessagesCache(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
REDIS_CLUSTER_EXTENSION.getRedisCluster(), sharedExecutorService, messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC()); REDIS_CLUSTER_EXTENSION.getRedisCluster(), sharedExecutorService, messageDeliveryScheduler, sharedExecutorService, Clock.systemUTC());
messagesDynamoDb = new MessagesDynamoDb(dynamoDbExtension.getDynamoDbClient(), messagesDynamoDb = new MessagesDynamoDb(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
dynamoDbExtension.getDynamoDbAsyncClient(), MessagesDynamoDbExtension.TABLE_NAME, Duration.ofDays(7), DYNAMO_DB_EXTENSION.getDynamoDbAsyncClient(), Tables.MESSAGES.tableName(), Duration.ofDays(7),
sharedExecutorService); sharedExecutorService);
reportMessageManager = mock(ReportMessageManager.class); reportMessageManager = mock(ReportMessageManager.class);
account = mock(Account.class); account = mock(Account.class);