mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 08:48:05 +01:00
Migrate username storage from a relational database to DynamoDB
This commit is contained in:
@@ -51,6 +51,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
private static final String ACCOUNTS_TABLE_NAME = "accounts_test";
|
||||
private static final String NUMBERS_TABLE_NAME = "numbers_test";
|
||||
private static final String PNI_ASSIGNMENT_TABLE_NAME = "pni_assignment_test";
|
||||
private static final String USERNAMES_TABLE_NAME = "usernames_test";
|
||||
private static final String PNI_TABLE_NAME = "pni_test";
|
||||
private static final String NEEDS_RECONCILIATION_INDEX_NAME = "needs_reconciliation_test";
|
||||
private static final String DELETED_ACCOUNTS_LOCK_TABLE_NAME = "deleted_accounts_lock_test";
|
||||
@@ -155,6 +156,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
ACCOUNTS_DYNAMO_EXTENSION.getTableName(),
|
||||
NUMBERS_TABLE_NAME,
|
||||
PNI_ASSIGNMENT_TABLE_NAME,
|
||||
USERNAMES_TABLE_NAME,
|
||||
SCAN_PAGE_SIZE);
|
||||
|
||||
{
|
||||
@@ -191,7 +193,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(UsernamesManager.class),
|
||||
mock(ReservedUsernames.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
secureStorageClient,
|
||||
|
||||
@@ -59,6 +59,7 @@ 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;
|
||||
|
||||
@@ -122,6 +123,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
dynamoDbExtension.getTableName(),
|
||||
NUMBERS_TABLE_NAME,
|
||||
PNI_TABLE_NAME,
|
||||
USERNAMES_TABLE_NAME,
|
||||
SCAN_PAGE_SIZE);
|
||||
|
||||
{
|
||||
@@ -148,7 +150,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(UsernamesManager.class),
|
||||
mock(ReservedUsernames.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
@@ -44,25 +45,14 @@ import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ContestedOptimisticLockException;
|
||||
import org.whispersystems.textsecuregcm.storage.DeletedAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PhoneNumberIdentifiers;
|
||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
|
||||
class AccountsManagerTest {
|
||||
@@ -73,10 +63,13 @@ class AccountsManagerTest {
|
||||
private Keys keys;
|
||||
private MessagesManager messagesManager;
|
||||
private ProfilesManager profilesManager;
|
||||
private ReservedUsernames reservedUsernames;
|
||||
|
||||
private RedisAdvancedClusterCommands<String, String> commands;
|
||||
private AccountsManager accountsManager;
|
||||
|
||||
private static final UUID RESERVED_FOR_UUID = UUID.randomUUID();
|
||||
|
||||
private static final Answer<?> ACCOUNT_UPDATE_ANSWER = (answer) -> {
|
||||
// it is implicit in the update() contract is that a successful call will
|
||||
// result in an incremented version
|
||||
@@ -93,6 +86,7 @@ class AccountsManagerTest {
|
||||
keys = mock(Keys.class);
|
||||
messagesManager = mock(MessagesManager.class);
|
||||
profilesManager = mock(ProfilesManager.class);
|
||||
reservedUsernames = mock(ReservedUsernames.class);
|
||||
|
||||
//noinspection unchecked
|
||||
commands = mock(RedisAdvancedClusterCommands.class);
|
||||
@@ -127,6 +121,13 @@ class AccountsManagerTest {
|
||||
return phoneNumberIdentifiersByE164.computeIfAbsent(number, n -> UUID.randomUUID());
|
||||
});
|
||||
|
||||
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
|
||||
mock(DynamicConfigurationManager.class);
|
||||
|
||||
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
@@ -135,7 +136,7 @@ class AccountsManagerTest {
|
||||
directoryQueue,
|
||||
keys,
|
||||
messagesManager,
|
||||
mock(UsernamesManager.class),
|
||||
reservedUsernames,
|
||||
profilesManager,
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
storageClient,
|
||||
@@ -207,6 +208,29 @@ class AccountsManagerTest {
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetByUsernameInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "test";
|
||||
|
||||
when(commands.get(eq("AccountMap::" + username))).thenReturn(uuid.toString());
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\", \"username\": \"test\"}");
|
||||
|
||||
Optional<Account> account = accountsManager.getByUsername(username);
|
||||
|
||||
assertTrue(account.isPresent());
|
||||
assertEquals(account.get().getNumber(), "+14152222222");
|
||||
assertEquals(account.get().getProfileName(), "test");
|
||||
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
|
||||
assertEquals(Optional.of(username), account.get().getUsername());
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + username));
|
||||
verify(commands).get(eq("Account3::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountByNumberNotInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
@@ -280,6 +304,33 @@ class AccountsManagerTest {
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountByUsernameNotInCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "test";
|
||||
|
||||
Account account = new Account("+14152222222", uuid, UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
account.setUsername(username);
|
||||
|
||||
when(commands.get(eq("AccountMap::" + username))).thenReturn(null);
|
||||
when(accounts.getByUsername(username)).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByUsername(username);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + username));
|
||||
verify(commands).setex(eq("AccountMap::" + username), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("AccountMap::" + account.getPhoneNumberIdentifier()), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts).getByUsername(username);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountByNumberBrokenCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
@@ -353,6 +404,33 @@ class AccountsManagerTest {
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountByUsernameBrokenCache() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "test";
|
||||
|
||||
Account account = new Account("+14152222222", uuid, UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
account.setUsername(username);
|
||||
|
||||
when(commands.get(eq("AccountMap::" + username))).thenThrow(new RedisException("OH NO"));
|
||||
when(accounts.getByUsername(username)).thenReturn(Optional.of(account));
|
||||
|
||||
Optional<Account> retrieved = accountsManager.getByUsername(username);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(commands).get(eq("AccountMap::" + username));
|
||||
verify(commands).setex(eq("AccountMap::" + username), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("AccountMap::" + account.getPhoneNumberIdentifier()), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
|
||||
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(accounts).getByUsername(username);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdate_optimisticLockingFailure() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
@@ -627,4 +705,54 @@ class AccountsManagerTest {
|
||||
|
||||
assertThrows(AssertionError.class, () -> accountsManager.update(account, a -> a.setNumber(targetNumber, UUID.randomUUID())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsername() throws UsernameNotAvailableException {
|
||||
final Account account = new Account("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
final String username = "test";
|
||||
|
||||
assertDoesNotThrow(() -> accountsManager.setUsername(account, username));
|
||||
verify(accounts).setUsername(account, username);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameSameUsername() throws UsernameNotAvailableException {
|
||||
final Account account = new Account("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
final String username = "test";
|
||||
account.setUsername(username);
|
||||
|
||||
assertDoesNotThrow(() -> accountsManager.setUsername(account, username));
|
||||
verify(accounts, never()).setUsername(eq(account), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameNotAvailable() throws UsernameNotAvailableException {
|
||||
final Account account = new Account("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
final String username = "test";
|
||||
|
||||
doThrow(new UsernameNotAvailableException()).when(accounts).setUsername(account, username);
|
||||
|
||||
assertThrows(UsernameNotAvailableException.class, () -> accountsManager.setUsername(account, username));
|
||||
verify(accounts).setUsername(account, username);
|
||||
|
||||
assertTrue(account.getUsername().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameReserved() {
|
||||
final String username = "reserved";
|
||||
when(reservedUsernames.isReserved(eq(username), any())).thenReturn(true);
|
||||
|
||||
final Account account = new Account("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
|
||||
assertThrows(UsernameNotAvailableException.class, () -> accountsManager.setUsername(account, username));
|
||||
assertTrue(account.getUsername().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameViaUpdate() {
|
||||
final Account account = new Account("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new HashSet<>(), new byte[16]);
|
||||
|
||||
assertThrows(AssertionError.class, () -> accountsManager.update(account, a -> a.setUsername("test")));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -19,7 +21,6 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -38,8 +39,6 @@ import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
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.CancellationReason;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException;
|
||||
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
||||
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
|
||||
@@ -61,6 +60,7 @@ 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 int SCAN_PAGE_SIZE = 1;
|
||||
|
||||
@@ -108,11 +108,27 @@ class AccountsTest {
|
||||
|
||||
dynamoDbExtension.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest);
|
||||
|
||||
CreateTableRequest createUsernamesTableRequest = CreateTableRequest.builder()
|
||||
.tableName(USERNAME_CONSTRAINT_TABLE_NAME)
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName(Accounts.ATTR_USERNAME)
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName(Accounts.ATTR_USERNAME)
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build())
|
||||
.provisionedThroughput(DynamoDbExtension.DEFAULT_PROVISIONED_THROUGHPUT)
|
||||
.build();
|
||||
|
||||
dynamoDbExtension.getDynamoDbClient().createTable(createUsernamesTableRequest);
|
||||
|
||||
this.accounts = new Accounts(
|
||||
dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getTableName(),
|
||||
NUMBER_CONSTRAINT_TABLE_NAME,
|
||||
PNI_CONSTRAINT_TABLE_NAME,
|
||||
USERNAME_CONSTRAINT_TABLE_NAME,
|
||||
SCAN_PAGE_SIZE);
|
||||
}
|
||||
|
||||
@@ -357,7 +373,7 @@ class AccountsTest {
|
||||
|
||||
final DynamoDbClient dynamoDbClient = mock(DynamoDbClient.class);
|
||||
accounts = new Accounts(dynamoDbClient,
|
||||
dynamoDbExtension.getTableName(), NUMBER_CONSTRAINT_TABLE_NAME, PNI_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE);
|
||||
dynamoDbExtension.getTableName(), NUMBER_CONSTRAINT_TABLE_NAME, PNI_CONSTRAINT_TABLE_NAME, USERNAME_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE);
|
||||
|
||||
when(dynamoDbClient.updateItem(any(UpdateItemRequest.class)))
|
||||
.thenThrow(TransactionConflictException.class);
|
||||
@@ -495,7 +511,7 @@ class AccountsTest {
|
||||
.thenThrow(RuntimeException.class);
|
||||
|
||||
Accounts accounts = new Accounts(client, ACCOUNTS_TABLE_NAME, NUMBER_CONSTRAINT_TABLE_NAME,
|
||||
PNI_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE);
|
||||
PNI_CONSTRAINT_TABLE_NAME, USERNAME_CONSTRAINT_TABLE_NAME, SCAN_PAGE_SIZE);
|
||||
Account account = generateAccount("+14151112222", UUID.randomUUID(), UUID.randomUUID());
|
||||
|
||||
try {
|
||||
@@ -646,6 +662,118 @@ class AccountsTest {
|
||||
assertThrows(TransactionCanceledException.class, () -> accounts.changeNumber(account, targetNumber, existingPhoneNumberIdentifier));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsername() throws UsernameNotAvailableException {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
final String username = "test";
|
||||
|
||||
assertThat(accounts.getByUsername(username)).isEmpty();
|
||||
|
||||
accounts.setUsername(account, username);
|
||||
|
||||
{
|
||||
final Optional<Account> maybeAccount = accounts.getByUsername(username);
|
||||
|
||||
assertThat(maybeAccount).hasValueSatisfying(retrievedAccount ->
|
||||
assertThat(retrievedAccount.getUsername()).hasValueSatisfying(retrievedUsername ->
|
||||
assertThat(retrievedUsername).isEqualTo(username)));
|
||||
|
||||
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(), maybeAccount.orElseThrow(), account);
|
||||
}
|
||||
|
||||
final String secondUsername = username + "2";
|
||||
|
||||
accounts.setUsername(account, secondUsername);
|
||||
|
||||
assertThat(accounts.getByUsername(username)).isEmpty();
|
||||
|
||||
{
|
||||
final Optional<Account> maybeAccount = accounts.getByUsername(secondUsername);
|
||||
|
||||
assertThat(maybeAccount).isPresent();
|
||||
verifyStoredState(account.getNumber(), account.getUuid(), account.getPhoneNumberIdentifier(),
|
||||
maybeAccount.get(), account);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameConflict() {
|
||||
final Account firstAccount = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
final Account secondAccount = generateAccount("+18005559876", UUID.randomUUID(), UUID.randomUUID());
|
||||
|
||||
accounts.create(firstAccount);
|
||||
accounts.create(secondAccount);
|
||||
|
||||
final String username = "test";
|
||||
|
||||
assertThatNoException().isThrownBy(() -> accounts.setUsername(firstAccount, username));
|
||||
|
||||
final Optional<Account> maybeAccount = accounts.getByUsername(username);
|
||||
|
||||
assertThat(maybeAccount).isPresent();
|
||||
verifyStoredState(firstAccount.getNumber(), firstAccount.getUuid(), firstAccount.getPhoneNumberIdentifier(), maybeAccount.get(), firstAccount);
|
||||
|
||||
assertThatExceptionOfType(UsernameNotAvailableException.class)
|
||||
.isThrownBy(() -> accounts.setUsername(secondAccount, username));
|
||||
|
||||
assertThat(secondAccount.getUsername()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameVersionMismatch() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
account.setVersion(account.getVersion() + 77);
|
||||
|
||||
assertThatExceptionOfType(ContestedOptimisticLockException.class)
|
||||
.isThrownBy(() -> accounts.setUsername(account, "test"));
|
||||
|
||||
assertThat(account.getUsername()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearUsername() throws UsernameNotAvailableException {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
final String username = "test";
|
||||
|
||||
accounts.setUsername(account, username);
|
||||
assertThat(accounts.getByUsername(username)).isPresent();
|
||||
|
||||
accounts.clearUsername(account);
|
||||
|
||||
assertThat(accounts.getByUsername(username)).isEmpty();
|
||||
assertThat(accounts.getByAccountIdentifier(account.getUuid()))
|
||||
.hasValueSatisfying(clearedAccount -> assertThat(clearedAccount.getUsername()).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearUsernameNoUsername() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
assertThatNoException().isThrownBy(() -> accounts.clearUsername(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearUsernameVersionMismatch() throws UsernameNotAvailableException {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
final String username = "test";
|
||||
|
||||
accounts.setUsername(account, username);
|
||||
|
||||
account.setVersion(account.getVersion() + 12);
|
||||
|
||||
assertThatExceptionOfType(ContestedOptimisticLockException.class).isThrownBy(() -> accounts.clearUsername(account));
|
||||
|
||||
assertThat(account.getUsername()).hasValueSatisfying(u -> assertThat(u).isEqualTo(username));
|
||||
}
|
||||
|
||||
private Device generateDevice(long id) {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
|
||||
|
||||
@@ -90,7 +90,7 @@ import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameNotAvailableException;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.Hex;
|
||||
@@ -141,7 +141,6 @@ class AccountControllerTest {
|
||||
private static RecaptchaClient recaptchaClient = mock(RecaptchaClient.class);
|
||||
private static GCMSender gcmSender = mock(GCMSender.class);
|
||||
private static APNSender apnSender = mock(APNSender.class);
|
||||
private static UsernamesManager usernamesManager = mock(UsernamesManager.class);
|
||||
|
||||
private static DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
|
||||
@@ -164,7 +163,6 @@ class AccountControllerTest {
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new AccountController(pendingAccountsManager,
|
||||
accountsManager,
|
||||
usernamesManager,
|
||||
abusiveHostRules,
|
||||
rateLimiters,
|
||||
smsSender,
|
||||
@@ -242,8 +240,8 @@ class AccountControllerTest {
|
||||
return account;
|
||||
});
|
||||
|
||||
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("n00bkiller"))).thenReturn(true);
|
||||
when(usernamesManager.put(eq(AuthHelper.VALID_UUID), eq("takenusername"))).thenReturn(false);
|
||||
when(accountsManager.setUsername(AuthHelper.VALID_ACCOUNT, "takenusername"))
|
||||
.thenThrow(new UsernameNotAvailableException());
|
||||
|
||||
{
|
||||
DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||
@@ -293,7 +291,6 @@ class AccountControllerTest {
|
||||
recaptchaClient,
|
||||
gcmSender,
|
||||
apnSender,
|
||||
usernamesManager,
|
||||
verifyExperimentEnrollmentManager);
|
||||
|
||||
clearInvocations(AuthHelper.DISABLED_DEVICE);
|
||||
@@ -1622,7 +1619,7 @@ class AccountControllerTest {
|
||||
.delete();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
verify(usernamesManager, times(1)).delete(eq(AuthHelper.VALID_UUID));
|
||||
verify(accountsManager).clearUsername(AuthHelper.VALID_ACCOUNT);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -66,7 +66,6 @@ import org.whispersystems.textsecuregcm.storage.AccountBadge;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.ProfilesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.VersionedProfile;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
@@ -80,7 +79,6 @@ class ProfileControllerTest {
|
||||
private static final Clock clock = mock(Clock.class);
|
||||
private static final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private static final ProfilesManager profilesManager = mock(ProfilesManager.class);
|
||||
private static final UsernamesManager usernamesManager = mock(UsernamesManager.class);
|
||||
private static final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
private static final RateLimiter rateLimiter = mock(RateLimiter.class);
|
||||
private static final RateLimiter usernameRateLimiter = mock(RateLimiter.class);
|
||||
@@ -107,7 +105,6 @@ class ProfileControllerTest {
|
||||
rateLimiters,
|
||||
accountsManager,
|
||||
profilesManager,
|
||||
usernamesManager,
|
||||
dynamicConfigurationManager,
|
||||
(acceptableLanguages, accountBadges, isSelf) -> List.of(new Badge("TEST", "other", "Test Badge",
|
||||
"This badge is in unit tests.", List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld")))
|
||||
@@ -156,6 +153,7 @@ class ProfileControllerTest {
|
||||
when(profileAccount.isAnnouncementGroupSupported()).thenReturn(false);
|
||||
when(profileAccount.isChangeNumberSupported()).thenReturn(false);
|
||||
when(profileAccount.getCurrentProfileVersion()).thenReturn(Optional.empty());
|
||||
when(profileAccount.getUsername()).thenReturn(Optional.of("n00bkiller"));
|
||||
|
||||
Account capabilitiesAccount = mock(Account.class);
|
||||
|
||||
@@ -171,8 +169,7 @@ class ProfileControllerTest {
|
||||
|
||||
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID_TWO)).thenReturn(Optional.of(profileAccount));
|
||||
when(usernamesManager.get(AuthHelper.VALID_UUID_TWO)).thenReturn(Optional.of("n00bkiller"));
|
||||
when(usernamesManager.get("n00bkiller")).thenReturn(Optional.of(AuthHelper.VALID_UUID_TWO));
|
||||
when(accountsManager.getByUsername("n00bkiller")).thenReturn(Optional.of(profileAccount));
|
||||
|
||||
when(accountsManager.getByE164(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(capabilitiesAccount));
|
||||
when(accountsManager.getByAccountIdentifier(AuthHelper.VALID_UUID)).thenReturn(Optional.of(capabilitiesAccount));
|
||||
@@ -183,7 +180,6 @@ class ProfileControllerTest {
|
||||
|
||||
clearInvocations(rateLimiter);
|
||||
clearInvocations(accountsManager);
|
||||
clearInvocations(usernamesManager);
|
||||
clearInvocations(usernameRateLimiter);
|
||||
clearInvocations(profilesManager);
|
||||
}
|
||||
@@ -209,7 +205,6 @@ class ProfileControllerTest {
|
||||
badge -> "Test Badge".equals(badge.getName()), "has badge with expected name"));
|
||||
|
||||
verify(accountsManager).getByAccountIdentifier(AuthHelper.VALID_UUID_TWO);
|
||||
verify(usernamesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO));
|
||||
verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID);
|
||||
}
|
||||
|
||||
@@ -229,8 +224,7 @@ class ProfileControllerTest {
|
||||
assertThat(profile.getBadges()).hasSize(1).element(0).has(new Condition<>(
|
||||
badge -> "Test Badge".equals(badge.getName()), "has badge with expected name"));
|
||||
|
||||
verify(accountsManager, times(1)).getByAccountIdentifier(eq(AuthHelper.VALID_UUID_TWO));
|
||||
verify(usernamesManager, times(1)).get(eq("n00bkiller"));
|
||||
verify(accountsManager).getByUsername("n00bkiller");
|
||||
verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID));
|
||||
}
|
||||
|
||||
@@ -265,8 +259,8 @@ class ProfileControllerTest {
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(404);
|
||||
|
||||
verify(usernamesManager, times(1)).get(eq("n00bkillerzzzzz"));
|
||||
verify(usernameRateLimiter, times(1)).validate(eq(AuthHelper.VALID_UUID));
|
||||
verify(accountsManager).getByUsername("n00bkillerzzzzz");
|
||||
verify(usernameRateLimiter).validate(eq(AuthHelper.VALID_UUID));
|
||||
}
|
||||
|
||||
|
||||
@@ -594,7 +588,6 @@ class ProfileControllerTest {
|
||||
badge -> "Test Badge".equals(badge.getName()), "has badge with expected name"));
|
||||
|
||||
verify(accountsManager, times(1)).getByAccountIdentifier(eq(AuthHelper.VALID_UUID_TWO));
|
||||
verify(usernamesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO));
|
||||
verify(profilesManager, times(1)).get(eq(AuthHelper.VALID_UUID_TWO), eq("validversion"));
|
||||
|
||||
verify(rateLimiter, times(1)).validate(AuthHelper.VALID_UUID);
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import io.lettuce.core.RedisException;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
|
||||
import org.whispersystems.textsecuregcm.storage.ReservedUsernames;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernamesManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.RedisClusterHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static junit.framework.TestCase.assertSame;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class UsernamesManagerTest {
|
||||
|
||||
@Test
|
||||
public void testGetByUsernameInCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUsername::n00bkiller"))).thenReturn(uuid.toString());
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertEquals(retrieved.get(), uuid);
|
||||
|
||||
verify(commands, times(1)).get(eq("UsernameByUsername::n00bkiller"));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUuidInCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUuid::" + uuid.toString()))).thenReturn("n00bkiller");
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertEquals(retrieved.get(), "n00bkiller");
|
||||
|
||||
verify(commands, times(1)).get(eq("UsernameByUuid::" + uuid.toString()));
|
||||
verifyNoMoreInteractions(commands);
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetByUsernameNotInCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUsername::n00bkiller"))).thenReturn(null);
|
||||
when(usernames.get(eq("n00bkiller"))).thenReturn(Optional.of(uuid));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), uuid);
|
||||
|
||||
verify(commands, times(1)).get(eq("UsernameByUsername::n00bkiller"));
|
||||
verify(commands, times(1)).set(eq("UsernameByUsername::n00bkiller"), eq(uuid.toString()));
|
||||
verify(commands, times(1)).set(eq("UsernameByUuid::" + uuid.toString()), eq("n00bkiller"));
|
||||
verify(commands, times(1)).get(eq("UsernameByUuid::" + uuid.toString()));
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(usernames, times(1)).get(eq("n00bkiller"));
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUuidNotInCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUuid::" + uuid.toString()))).thenReturn(null);
|
||||
when(usernames.get(eq(uuid))).thenReturn(Optional.of("n00bkiller"));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertEquals(retrieved.get(), "n00bkiller");
|
||||
|
||||
verify(commands, times(2)).get(eq("UsernameByUuid::" + uuid));
|
||||
verify(commands, times(1)).set(eq("UsernameByUuid::" + uuid), eq("n00bkiller"));
|
||||
verify(commands, times(1)).set(eq("UsernameByUsername::n00bkiller"), eq(uuid.toString()));
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(usernames, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUsernameBrokenCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUsername::n00bkiller"))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(usernames.get(eq("n00bkiller"))).thenReturn(Optional.of(uuid));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<UUID> retrieved = usernamesManager.get("n00bkiller");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertEquals(retrieved.get(), uuid);
|
||||
|
||||
verify(commands, times(1)).get(eq("UsernameByUsername::n00bkiller"));
|
||||
verify(commands, times(1)).set(eq("UsernameByUsername::n00bkiller"), eq(uuid.toString()));
|
||||
verify(commands, times(1)).set(eq("UsernameByUuid::" + uuid.toString()), eq("n00bkiller"));
|
||||
verify(commands, times(1)).get(eq("UsernameByUuid::" + uuid.toString()));
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(usernames, times(1)).get(eq("n00bkiller"));
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountByUuidBrokenCache() {
|
||||
RedisAdvancedClusterCommands<String, String> commands = mock(RedisAdvancedClusterCommands.class);
|
||||
FaultTolerantRedisCluster cacheCluster = RedisClusterHelper.buildMockRedisCluster(commands);
|
||||
Usernames usernames = mock(Usernames.class);
|
||||
ReservedUsernames reserved = mock(ReservedUsernames.class);
|
||||
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
when(commands.get(eq("UsernameByUuid::" + uuid))).thenThrow(new RedisException("Connection lost!"));
|
||||
when(usernames.get(eq(uuid))).thenReturn(Optional.of("n00bkiller"));
|
||||
|
||||
UsernamesManager usernamesManager = new UsernamesManager(usernames, reserved, cacheCluster);
|
||||
Optional<String> retrieved = usernamesManager.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertEquals(retrieved.get(), "n00bkiller");
|
||||
|
||||
verify(commands, times(2)).get(eq("UsernameByUuid::" + uuid));
|
||||
verifyNoMoreInteractions(commands);
|
||||
|
||||
verify(usernames, times(1)).get(eq(uuid));
|
||||
verifyNoMoreInteractions(usernames);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2020 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.Usernames;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class UsernamesTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private Usernames usernames;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("usernamesTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration());
|
||||
|
||||
this.usernames = new Usernames(faultTolerantDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws SQLException, IOException {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "myusername";
|
||||
|
||||
assertTrue(usernames.put(uuid, username));
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM usernames WHERE uuid = ?");
|
||||
verifyStoredState(statement, uuid, username);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutChange() throws SQLException, IOException {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String firstUsername = "myfirstusername";
|
||||
String secondUsername = "mysecondusername";
|
||||
|
||||
assertTrue(usernames.put(uuid, firstUsername));
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM usernames WHERE uuid = ?");
|
||||
verifyStoredState(statement, uuid, firstUsername);
|
||||
|
||||
assertTrue(usernames.put(uuid, secondUsername));
|
||||
|
||||
verifyStoredState(statement, uuid, secondUsername);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutConflict() throws SQLException {
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
String username = "myfirstusername";
|
||||
|
||||
assertTrue(usernames.put(firstUuid, username));
|
||||
assertFalse(usernames.put(secondUuid, username));
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM usernames WHERE username = ?");
|
||||
statement.setString(1, username);
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
assertTrue(resultSet.next());
|
||||
assertThat(resultSet.getString("uuid")).isEqualTo(firstUuid.toString());
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUuid() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "myusername";
|
||||
|
||||
assertTrue(usernames.put(uuid, username));
|
||||
|
||||
Optional<String> retrieved = usernames.get(uuid);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertThat(retrieved.get()).isEqualTo(username);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUuidMissing() {
|
||||
Optional<String> retrieved = usernames.get(UUID.randomUUID());
|
||||
assertFalse(retrieved.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUsername() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "myusername";
|
||||
|
||||
assertTrue(usernames.put(uuid, username));
|
||||
|
||||
Optional<UUID> retrieved = usernames.get(username);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertThat(retrieved.get()).isEqualTo(uuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByUsernameMissing() {
|
||||
Optional<UUID> retrieved = usernames.get("myusername");
|
||||
|
||||
assertFalse(retrieved.isPresent());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
String username = "myusername";
|
||||
|
||||
assertTrue(usernames.put(uuid, username));
|
||||
|
||||
Optional<UUID> retrieved = usernames.get(username);
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertThat(retrieved.get()).isEqualTo(uuid);
|
||||
|
||||
usernames.delete(uuid);
|
||||
|
||||
assertThat(usernames.get(uuid).isPresent()).isFalse();
|
||||
}
|
||||
|
||||
private void verifyStoredState(PreparedStatement statement, UUID uuid, String expectedUsername)
|
||||
throws SQLException, IOException
|
||||
{
|
||||
statement.setObject(1, uuid);
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
if (resultSet.next()) {
|
||||
String data = resultSet.getString("username");
|
||||
assertThat(data).isNotEmpty();
|
||||
assertThat(data).isEqualTo(expectedUsername);
|
||||
} else {
|
||||
throw new AssertionError("No data");
|
||||
}
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -103,6 +103,10 @@ public class AccountsHelper {
|
||||
when(updatedAccount.getNumber()).thenAnswer(stubbing);
|
||||
break;
|
||||
}
|
||||
case "getUsername": {
|
||||
when(updatedAccount.getUsername()).thenAnswer(stubbing);
|
||||
break;
|
||||
}
|
||||
case "getDevices": {
|
||||
when(updatedAccount.getDevices())
|
||||
.thenAnswer(stubbing);
|
||||
|
||||
Reference in New Issue
Block a user