Remove some secondary keys from account cache

Remove e164, usernameHash, and usernameLink secondary mappings from the
accounts redis cache.
This commit is contained in:
Ravi Khadiwala 2024-01-10 11:05:57 -06:00 committed by ravi-signal
parent bf05e47e26
commit 323bfd9a6e
3 changed files with 53 additions and 285 deletions

View File

@ -30,7 +30,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Queue; import java.util.Queue;
@ -85,9 +84,6 @@ public class AccountsManager {
private static final Timer deleteTimer = metricRegistry.timer(name(AccountsManager.class, "delete")); private static final Timer deleteTimer = metricRegistry.timer(name(AccountsManager.class, "delete"));
private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet")); private static final Timer redisSetTimer = metricRegistry.timer(name(AccountsManager.class, "redisSet"));
private static final Timer redisNumberGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisNumberGet"));
private static final Timer redisUsernameHashGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUsernameHashGet"));
private static final Timer redisUsernameLinkHandleGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUsernameLinkHandleGet"));
private static final Timer redisPniGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisPniGet")); private static final Timer redisPniGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisPniGet"));
private static final Timer redisUuidGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUuidGet")); private static final Timer redisUuidGetTimer = metricRegistry.timer(name(AccountsManager.class, "redisUuidGet"));
private static final Timer redisDeleteTimer = metricRegistry.timer(name(AccountsManager.class, "redisDelete")); private static final Timer redisDeleteTimer = metricRegistry.timer(name(AccountsManager.class, "redisDelete"));
@ -875,19 +871,13 @@ public class AccountsManager {
} }
public Optional<Account> getByE164(final String number) { public Optional<Account> getByE164(final String number) {
return checkRedisThenAccounts( return getByNumberTimer.timeSupplier(() -> accounts.getByE164(number));
getByNumberTimer,
() -> redisGetBySecondaryKey(getAccountMapKey(number), redisNumberGetTimer),
() -> accounts.getByE164(number)
);
} }
public CompletableFuture<Optional<Account>> getByE164Async(final String number) { public CompletableFuture<Optional<Account>> getByE164Async(final String number) {
return checkRedisThenAccountsAsync( final Timer.Context context = getByNumberTimer.time();
getByNumberTimer, return accounts.getByE164Async(number)
() -> redisGetBySecondaryKeyAsync(getAccountMapKey(number), redisNumberGetTimer), .whenComplete((ignoredResult, ignoredThrowable) -> context.close());
() -> accounts.getByE164Async(number)
);
} }
public Optional<Account> getByPhoneNumberIdentifier(final UUID pni) { public Optional<Account> getByPhoneNumberIdentifier(final UUID pni) {
@ -907,19 +897,15 @@ public class AccountsManager {
} }
public CompletableFuture<Optional<Account>> getByUsernameLinkHandle(final UUID usernameLinkHandle) { public CompletableFuture<Optional<Account>> getByUsernameLinkHandle(final UUID usernameLinkHandle) {
return checkRedisThenAccountsAsync( final Timer.Context context = getByUsernameLinkHandleTimer.time();
getByUsernameLinkHandleTimer, return accounts.getByUsernameLinkHandle(usernameLinkHandle)
() -> redisGetBySecondaryKeyAsync(getAccountMapKey(usernameLinkHandle.toString()), redisUsernameLinkHandleGetTimer), .whenComplete((ignoredResult, ignoredThrowable) -> context.close());
() -> accounts.getByUsernameLinkHandle(usernameLinkHandle)
);
} }
public CompletableFuture<Optional<Account>> getByUsernameHash(final byte[] usernameHash) { public CompletableFuture<Optional<Account>> getByUsernameHash(final byte[] usernameHash) {
return checkRedisThenAccountsAsync( final Timer.Context context = getByUsernameHashTimer.time();
getByUsernameHashTimer, return accounts.getByUsernameHash(usernameHash)
() -> redisGetBySecondaryKeyAsync(getUsernameHashAccountMapKey(usernameHash), redisUsernameHashGetTimer), .whenComplete((ignoredResult, ignoredThrowable) -> context.close());
() -> accounts.getByUsernameHash(usernameHash)
);
} }
public Optional<Account> getByServiceIdentifier(final ServiceIdentifier serviceIdentifier) { public Optional<Account> getByServiceIdentifier(final ServiceIdentifier serviceIdentifier) {
@ -1030,11 +1016,7 @@ public class AccountsManager {
final RedisAdvancedClusterCommands<String, String> commands = connection.sync(); final RedisAdvancedClusterCommands<String, String> commands = connection.sync();
commands.setex(getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS, account.getUuid().toString()); commands.setex(getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS, account.getUuid().toString());
commands.setex(getAccountMapKey(account.getNumber()), CACHE_TTL_SECONDS, account.getUuid().toString());
commands.setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson); commands.setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson);
account.getUsernameHash().ifPresent(usernameHash ->
commands.setex(getUsernameHashAccountMapKey(usernameHash), CACHE_TTL_SECONDS, account.getUuid().toString()));
}); });
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
@ -1055,20 +1037,8 @@ public class AccountsManager {
getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS, getAccountMapKey(account.getPhoneNumberIdentifier().toString()), CACHE_TTL_SECONDS,
account.getUuid().toString()) account.getUuid().toString())
.toCompletableFuture(), .toCompletableFuture(),
connection.async()
.setex(getAccountMapKey(account.getNumber()), CACHE_TTL_SECONDS, account.getUuid().toString())
.toCompletableFuture(),
connection.async().setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson) connection.async().setex(getAccountEntityKey(account.getUuid()), CACHE_TTL_SECONDS, accountJson)
.toCompletableFuture(), .toCompletableFuture()));
account.getUsernameHash()
.map(usernameHash -> connection.async()
.setex(getUsernameHashAccountMapKey(usernameHash), CACHE_TTL_SECONDS, account.getUuid().toString())
.toCompletableFuture())
.orElseGet(() -> CompletableFuture.completedFuture(null))
));
} }
private Optional<Account> checkRedisThenAccounts( private Optional<Account> checkRedisThenAccounts(
@ -1186,11 +1156,8 @@ public class AccountsManager {
try (final Timer.Context ignored = redisDeleteTimer.time()) { try (final Timer.Context ignored = redisDeleteTimer.time()) {
cacheCluster.useCluster(connection -> { cacheCluster.useCluster(connection -> {
connection.sync().del( connection.sync().del(
getAccountMapKey(account.getNumber()),
getAccountMapKey(account.getPhoneNumberIdentifier().toString()), getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
getAccountEntityKey(account.getUuid())); getAccountEntityKey(account.getUuid()));
account.getUsernameHash().ifPresent(usernameHash -> connection.sync().del(getUsernameHashAccountMapKey(usernameHash)));
}); });
} }
} }
@ -1198,15 +1165,14 @@ public class AccountsManager {
private CompletableFuture<Void> redisDeleteAsync(final Account account) { private CompletableFuture<Void> redisDeleteAsync(final Account account) {
@SuppressWarnings("resource") final Timer.Context timerContext = redisDeleteTimer.time(); @SuppressWarnings("resource") final Timer.Context timerContext = redisDeleteTimer.time();
final List<String> keysToDelete = new ArrayList<>(4); final String[] keysToDelete = new String[]{
keysToDelete.add(getAccountMapKey(account.getNumber())); getAccountMapKey(account.getPhoneNumberIdentifier().toString()),
keysToDelete.add(getAccountMapKey(account.getPhoneNumberIdentifier().toString())); getAccountEntityKey(account.getUuid())
keysToDelete.add(getAccountEntityKey(account.getUuid())); };
account.getUsernameHash().ifPresent(usernameHash -> keysToDelete.add(getUsernameHashAccountMapKey(usernameHash))); return cacheCluster.withCluster(connection -> connection.async().del(keysToDelete))
return cacheCluster.withCluster(connection -> connection.async().del(keysToDelete.toArray(new String[0])))
.toCompletableFuture() .toCompletableFuture()
.thenRun(timerContext::close); .whenComplete((ignoredResult, ignoredException) -> timerContext.close())
.thenRun(Util.NOOP);
} }
} }

View File

@ -280,51 +280,6 @@ class AccountsManagerTest {
assertFalse(accountsManager.getByServiceIdentifierAsync(new PniServiceIdentifier(aci)).join().isPresent()); assertFalse(accountsManager.getByServiceIdentifierAsync(new PniServiceIdentifier(aci)).join().isPresent());
} }
@Test
void testGetAccountByNumberInCache() {
UUID uuid = UUID.randomUUID();
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(uuid.toString());
when(commands.get(eq("Account3::" + uuid))).thenReturn(
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}");
Optional<Account> account = accountsManager.getByE164("+14152222222");
assertTrue(account.isPresent());
assertEquals(account.get().getNumber(), "+14152222222");
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).get(eq("Account3::" + uuid));
verifyNoMoreInteractions(commands);
verifyNoInteractions(accounts);
}
@Test
void testGetAccountByNumberAsyncInCache() {
UUID uuid = UUID.randomUUID();
when(asyncCommands.get(eq("AccountMap::+14152222222")))
.thenReturn(MockRedisFuture.completedFuture(uuid.toString()));
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
"{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\"}"));
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
Optional<Account> account = accountsManager.getByE164Async("+14152222222").join();
assertTrue(account.isPresent());
assertEquals(account.get().getNumber(), "+14152222222");
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
verify(asyncCommands).get(eq("AccountMap::+14152222222"));
verify(asyncCommands).get(eq("Account3::" + uuid));
verifyNoMoreInteractions(asyncCommands);
verifyNoInteractions(accounts);
}
@Test @Test
void testGetAccountByUuidInCache() { void testGetAccountByUuidInCache() {
@ -416,80 +371,6 @@ class AccountsManagerTest {
verifyNoInteractions(accounts); verifyNoInteractions(accounts);
} }
@Test
void testGetAccountByUsernameHashInCache() {
UUID uuid = UUID.randomUUID();
when(asyncCommands.get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1)))
.thenReturn(MockRedisFuture.completedFuture(uuid.toString()));
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(MockRedisFuture.completedFuture(
String.format("{\"number\": \"+14152222222\", \"pni\": \"de24dc73-fbd8-41be-a7d5-764c70d9da7e\", \"usernameHash\": \"%s\"}",
BASE_64_URL_USERNAME_HASH_1)));
Optional<Account> account = accountsManager.getByUsernameHash(USERNAME_HASH_1).join();
assertTrue(account.isPresent());
assertEquals(account.get().getNumber(), "+14152222222");
assertEquals(UUID.fromString("de24dc73-fbd8-41be-a7d5-764c70d9da7e"), account.get().getPhoneNumberIdentifier());
assertArrayEquals(USERNAME_HASH_1, account.get().getUsernameHash().get());
verify(asyncCommands).get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1));
verify(asyncCommands).get(eq("Account3::" + uuid));
verifyNoMoreInteractions(asyncCommands);
verifyNoInteractions(accounts);
}
@Test
void testGetAccountByNumberNotInCache() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(commands.get(eq("AccountMap::+14152222222"))).thenReturn(null);
when(accounts.getByE164(eq("+14152222222"))).thenReturn(Optional.of(account));
Optional<Account> retrieved = accountsManager.getByE164("+14152222222");
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).getByE164(eq("+14152222222"));
verifyNoMoreInteractions(accounts);
}
@Test
void testGetAccountByNumberNotInCacheAsync() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(asyncCommands.get(eq("AccountMap::+14152222222"))).thenReturn(MockRedisFuture.completedFuture(null));
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
when(accounts.getByE164Async(eq("+14152222222")))
.thenReturn(MockRedisFuture.completedFuture(Optional.of(account)));
Optional<Account> retrieved = accountsManager.getByE164Async("+14152222222").join();
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("AccountMap::+14152222222"));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands);
verify(accounts).getByE164Async(eq("+14152222222"));
verifyNoMoreInteractions(accounts);
}
@Test @Test
void testGetAccountByUuidNotInCache() { void testGetAccountByUuidNotInCache() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
@ -505,7 +386,6 @@ class AccountsManagerTest {
assertSame(retrieved.get(), account); assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("Account3::" + uuid)); verify(commands, times(1)).get(eq("Account3::" + uuid));
verify(commands, times(1)).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands); verifyNoMoreInteractions(commands);
@ -531,7 +411,6 @@ class AccountsManagerTest {
assertSame(retrieved.get(), account); assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("Account3::" + uuid)); verify(asyncCommands).get(eq("Account3::" + uuid));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands); verifyNoMoreInteractions(asyncCommands);
@ -557,7 +436,6 @@ class AccountsManagerTest {
verify(commands).get(eq("AccountMap::" + pni)); verify(commands).get(eq("AccountMap::" + pni));
verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands); verifyNoMoreInteractions(commands);
@ -584,7 +462,6 @@ class AccountsManagerTest {
verify(asyncCommands).get(eq("AccountMap::" + pni)); verify(asyncCommands).get(eq("AccountMap::" + pni));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands); verifyNoMoreInteractions(asyncCommands);
@ -593,86 +470,20 @@ class AccountsManagerTest {
} }
@Test @Test
void testGetAccountByUsernameHashNotInCache() { void testGetAccountByUsernameHash() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(),
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]); new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
account.setUsernameHash(USERNAME_HASH_1); account.setUsernameHash(USERNAME_HASH_1);
when(asyncCommands.get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1)))
.thenReturn(MockRedisFuture.completedFuture(null));
when(accounts.getByUsernameHash(USERNAME_HASH_1)) when(accounts.getByUsernameHash(USERNAME_HASH_1))
.thenReturn(CompletableFuture.completedFuture(Optional.of(account))); .thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
Optional<Account> retrieved = accountsManager.getByUsernameHash(USERNAME_HASH_1).join(); Optional<Account> retrieved = accountsManager.getByUsernameHash(USERNAME_HASH_1).join();
assertTrue(retrieved.isPresent()); assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account); assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1));
verify(asyncCommands).setex(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + account.getPhoneNumberIdentifier()), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands);
verify(accounts).getByUsernameHash(USERNAME_HASH_1); verify(accounts).getByUsernameHash(USERNAME_HASH_1);
verifyNoMoreInteractions(accounts); verifyNoMoreInteractions(accounts);
} }
@Test
void testGetAccountByNumberBrokenCache() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(commands.get(eq("AccountMap::+14152222222"))).thenThrow(new RedisException("Connection lost!"));
when(accounts.getByE164(eq("+14152222222"))).thenReturn(Optional.of(account));
Optional<Account> retrieved = accountsManager.getByE164("+14152222222");
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("AccountMap::+14152222222"));
verify(commands, times(1)).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands);
verify(accounts, times(1)).getByE164(eq("+14152222222"));
verifyNoMoreInteractions(accounts);
}
@Test
void testGetAccountByNumberBrokenCacheAsync() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
when(asyncCommands.get(eq("AccountMap::+14152222222")))
.thenReturn(MockRedisFuture.failedFuture(new RedisException("Connection lost!")));
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
when(accounts.getByE164Async(eq("+14152222222"))).thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
Optional<Account> retrieved = accountsManager.getByE164Async("+14152222222").join();
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("AccountMap::+14152222222"));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands);
verify(accounts).getByE164Async(eq("+14152222222"));
verifyNoMoreInteractions(accounts);
}
@Test @Test
void testGetAccountByUuidBrokenCache() { void testGetAccountByUuidBrokenCache() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();
@ -688,7 +499,6 @@ class AccountsManagerTest {
assertSame(retrieved.get(), account); assertSame(retrieved.get(), account);
verify(commands, times(1)).get(eq("Account3::" + uuid)); verify(commands, times(1)).get(eq("Account3::" + uuid));
verify(commands, times(1)).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(commands, times(1)).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(commands, times(1)).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands); verifyNoMoreInteractions(commands);
@ -717,7 +527,6 @@ class AccountsManagerTest {
assertSame(retrieved.get(), account); assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("Account3::" + uuid)); verify(asyncCommands).get(eq("Account3::" + uuid));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands); verifyNoMoreInteractions(asyncCommands);
@ -743,7 +552,6 @@ class AccountsManagerTest {
verify(commands).get(eq("AccountMap::" + pni)); verify(commands).get(eq("AccountMap::" + pni));
verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(commands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(commands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(commands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(commands); verifyNoMoreInteractions(commands);
@ -773,7 +581,6 @@ class AccountsManagerTest {
verify(asyncCommands).get(eq("AccountMap::" + pni)); verify(asyncCommands).get(eq("AccountMap::" + pni));
verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString())); verify(asyncCommands).setex(eq("AccountMap::" + pni), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString()); verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands); verifyNoMoreInteractions(asyncCommands);
@ -781,35 +588,6 @@ class AccountsManagerTest {
verifyNoMoreInteractions(accounts); verifyNoMoreInteractions(accounts);
} }
@Test
void testGetAccountByUsernameBrokenCache() {
UUID uuid = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(), new byte[UnidentifiedAccessUtil.UNIDENTIFIED_ACCESS_KEY_LENGTH]);
account.setUsernameHash(USERNAME_HASH_1);
when(asyncCommands.get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1)))
.thenReturn(MockRedisFuture.failedFuture(new RedisException("OH NO")));
when(accounts.getByUsernameHash(USERNAME_HASH_1))
.thenReturn(CompletableFuture.completedFuture(Optional.of(account)));
Optional<Account> retrieved = accountsManager.getByUsernameHash(USERNAME_HASH_1).join();
assertTrue(retrieved.isPresent());
assertSame(retrieved.get(), account);
verify(asyncCommands).get(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1));
verify(asyncCommands).setex(eq("UAccountMap::" + BASE_64_URL_USERNAME_HASH_1), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::" + account.getPhoneNumberIdentifier()), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("AccountMap::+14152222222"), anyLong(), eq(uuid.toString()));
verify(asyncCommands).setex(eq("Account3::" + uuid), anyLong(), anyString());
verifyNoMoreInteractions(asyncCommands);
verify(accounts).getByUsernameHash(USERNAME_HASH_1);
verifyNoMoreInteractions(accounts);
}
@Test @Test
void testUpdate_optimisticLockingFailure() { void testUpdate_optimisticLockingFailure() {
UUID uuid = UUID.randomUUID(); UUID uuid = UUID.randomUUID();

View File

@ -11,11 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.time.Clock; import java.time.Clock;
@ -31,7 +28,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executors;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -135,21 +132,26 @@ class AccountsManagerUsernameIntegrationTest {
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME))) when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME)))
.thenReturn(true); .thenReturn(true);
final MessagesManager messageManager = mock(MessagesManager.class);
final ProfilesManager profileManager = mock(ProfilesManager.class);
when(messageManager.clear(any())).thenReturn(CompletableFuture.completedFuture(null));
when(profileManager.deleteAll(any())).thenReturn(CompletableFuture.completedFuture(null));
accountsManager = new AccountsManager( accountsManager = new AccountsManager(
accounts, accounts,
phoneNumberIdentifiers, phoneNumberIdentifiers,
CACHE_CLUSTER_EXTENSION.getRedisCluster(), CACHE_CLUSTER_EXTENSION.getRedisCluster(),
accountLockManager, accountLockManager,
keysManager, keysManager,
mock(MessagesManager.class), messageManager,
mock(ProfilesManager.class), profileManager,
mock(SecureStorageClient.class), mock(SecureStorageClient.class),
mock(SecureValueRecovery2Client.class), mock(SecureValueRecovery2Client.class),
mock(ClientPresenceManager.class), mock(ClientPresenceManager.class),
experimentEnrollmentManager, experimentEnrollmentManager,
mock(RegistrationRecoveryPasswordsManager.class), mock(RegistrationRecoveryPasswordsManager.class),
mock(Executor.class), Executors.newSingleThreadExecutor(),
mock(Executor.class), Executors.newSingleThreadExecutor(),
mock(Clock.class)); mock(Clock.class));
} }
@ -299,6 +301,28 @@ class AccountsManagerUsernameIntegrationTest {
assertArrayEquals(account.getEncryptedUsername().orElseThrow(), ENCRYPTED_USERNAME_2); assertArrayEquals(account.getEncryptedUsername().orElseThrow(), ENCRYPTED_USERNAME_2);
} }
@Test
public void testReclaim() throws InterruptedException {
Account account = AccountsHelper.createAccount(accountsManager, "+18005551111");
final AccountsManager.UsernameReservation reservation1 =
accountsManager.reserveUsernameHash(account, List.of(USERNAME_HASH_1)).join();
account = accountsManager.confirmReservedUsernameHash(reservation1.account(), USERNAME_HASH_1, ENCRYPTED_USERNAME_1)
.join();
// "reclaim" the account by re-registering
Account reclaimed = AccountsHelper.createAccount(accountsManager, "+18005551111");
// the username should still be reserved, but no longer on our account.
assertThat(reclaimed.getUsernameHash()).isEmpty();
// Make sure we can't lookup the account
assertThat(accountsManager.getByUsernameHash(USERNAME_HASH_1).join()).isEmpty();
// confirm it again
accountsManager.confirmReservedUsernameHash(reclaimed, USERNAME_HASH_1, ENCRYPTED_USERNAME_1).join();
assertThat(accountsManager.getByUsernameHash(USERNAME_HASH_1).join()).isPresent();
}
@Test @Test
public void testUsernameLinks() throws InterruptedException, AccountAlreadyExistsException { public void testUsernameLinks() throws InterruptedException, AccountAlreadyExistsException {
final Account account = AccountsHelper.createAccount(accountsManager, "+18005551111"); final Account account = AccountsHelper.createAccount(accountsManager, "+18005551111");