mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 21:48:06 +01:00
Perform cleanup operations before overwriting an existing account record
This commit is contained in:
@@ -67,6 +67,7 @@ public class AccountCreationIntegrationTest {
|
||||
private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneId.systemDefault());
|
||||
|
||||
private ExecutorService accountLockExecutor;
|
||||
private ExecutorService clientPresenceExecutor;
|
||||
|
||||
private AccountsManager accountsManager;
|
||||
private KeysManager keysManager;
|
||||
@@ -99,6 +100,7 @@ public class AccountCreationIntegrationTest {
|
||||
DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS.tableName());
|
||||
|
||||
accountLockExecutor = Executors.newSingleThreadExecutor();
|
||||
clientPresenceExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||
DynamoDbExtensionSchema.Tables.DELETED_ACCOUNTS_LOCK.tableName());
|
||||
@@ -139,15 +141,20 @@ public class AccountCreationIntegrationTest {
|
||||
mock(ExperimentEnrollmentManager.class),
|
||||
registrationRecoveryPasswordsManager,
|
||||
accountLockExecutor,
|
||||
clientPresenceExecutor,
|
||||
CLOCK);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws InterruptedException {
|
||||
accountLockExecutor.shutdown();
|
||||
clientPresenceExecutor.shutdown();
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@CartesianTest
|
||||
|
||||
@@ -64,6 +64,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
|
||||
private ClientPresenceManager clientPresenceManager;
|
||||
private ExecutorService accountLockExecutor;
|
||||
private ExecutorService clientPresenceExecutor;
|
||||
|
||||
private AccountsManager accountsManager;
|
||||
|
||||
@@ -95,6 +96,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
Tables.DELETED_ACCOUNTS.tableName());
|
||||
|
||||
accountLockExecutor = Executors.newSingleThreadExecutor();
|
||||
clientPresenceExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
final AccountLockManager accountLockManager = new AccountLockManager(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||
Tables.DELETED_ACCOUNTS_LOCK.tableName());
|
||||
@@ -136,6 +138,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
mock(ExperimentEnrollmentManager.class),
|
||||
registrationRecoveryPasswordsManager,
|
||||
accountLockExecutor,
|
||||
clientPresenceExecutor,
|
||||
mock(Clock.class));
|
||||
}
|
||||
}
|
||||
@@ -143,9 +146,13 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
@AfterEach
|
||||
void tearDown() throws InterruptedException {
|
||||
accountLockExecutor.shutdown();
|
||||
clientPresenceExecutor.shutdown();
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
accountLockExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
clientPresenceExecutor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -141,6 +141,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
mock(ExperimentEnrollmentManager.class),
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
mock(Executor.class),
|
||||
mock(Executor.class),
|
||||
mock(Clock.class)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -134,6 +135,15 @@ class AccountsManagerTest {
|
||||
profilesManager = mock(ProfilesManager.class);
|
||||
clientPresenceManager = mock(ClientPresenceManager.class);
|
||||
|
||||
final Executor clientPresenceExecutor = mock(Executor.class);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
final Runnable runnable = invocation.getArgument(0);
|
||||
runnable.run();
|
||||
|
||||
return null;
|
||||
}).when(clientPresenceExecutor).execute(any());
|
||||
|
||||
//noinspection unchecked
|
||||
commands = mock(RedisAdvancedClusterCommands.class);
|
||||
|
||||
@@ -224,6 +234,7 @@ class AccountsManagerTest {
|
||||
enrollmentManager,
|
||||
registrationRecoveryPasswordsManager,
|
||||
mock(Executor.class),
|
||||
clientPresenceExecutor,
|
||||
mock(Clock.class));
|
||||
}
|
||||
|
||||
@@ -856,7 +867,7 @@ class AccountsManagerTest {
|
||||
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
|
||||
when(accounts.getByAccountIdentifier(uuid)).thenReturn(Optional.empty())
|
||||
.thenReturn(Optional.of(account));
|
||||
when(accounts.create(any(), any())).thenThrow(ContestedOptimisticLockException.class);
|
||||
when(accounts.create(any(), any(), any())).thenThrow(ContestedOptimisticLockException.class);
|
||||
|
||||
accountsManager.update(account, a -> {
|
||||
});
|
||||
@@ -974,14 +985,14 @@ class AccountsManagerTest {
|
||||
|
||||
@Test
|
||||
void testCreateFreshAccount() throws InterruptedException {
|
||||
when(accounts.create(any(), any())).thenReturn(true);
|
||||
when(accounts.create(any(), any(), any())).thenReturn(true);
|
||||
|
||||
final String e164 = "+18005550123";
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 1, 2, null, null, true, null);
|
||||
|
||||
createAccount(e164, attributes);
|
||||
|
||||
verify(accounts).create(argThat(account -> e164.equals(account.getNumber())), any());
|
||||
verify(accounts).create(argThat(account -> e164.equals(account.getNumber())), any(), any());
|
||||
|
||||
verifyNoInteractions(messagesManager);
|
||||
verifyNoInteractions(profilesManager);
|
||||
@@ -991,25 +1002,31 @@ class AccountsManagerTest {
|
||||
void testReregisterAccount() throws InterruptedException {
|
||||
final UUID existingUuid = UUID.randomUUID();
|
||||
|
||||
when(accounts.create(any(), any())).thenAnswer(invocation -> {
|
||||
invocation.getArgument(0, Account.class).setUuid(existingUuid);
|
||||
return false;
|
||||
});
|
||||
|
||||
final String e164 = "+18005550123";
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 1, 2, null, null, true, null);
|
||||
|
||||
when(accounts.create(any(), any(), any())).thenAnswer(invocation -> {
|
||||
invocation.getArgument(0, Account.class).setUuid(existingUuid);
|
||||
|
||||
final BiFunction<UUID, UUID, CompletableFuture<Void>> cleanupOperation = invocation.getArgument(2);
|
||||
cleanupOperation.apply(existingUuid, phoneNumberIdentifiersByE164.get(e164));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
createAccount(e164, attributes);
|
||||
|
||||
assertTrue(phoneNumberIdentifiersByE164.containsKey(e164));
|
||||
|
||||
verify(accounts)
|
||||
.create(argThat(account -> e164.equals(account.getNumber()) && existingUuid.equals(account.getUuid())), any());
|
||||
.create(argThat(account -> e164.equals(account.getNumber()) && existingUuid.equals(account.getUuid())), any(), any());
|
||||
|
||||
verify(keysManager).delete(existingUuid);
|
||||
verify(keysManager).delete(phoneNumberIdentifiersByE164.get(e164));
|
||||
verify(keysManager).delete(existingUuid, true);
|
||||
verify(keysManager).delete(phoneNumberIdentifiersByE164.get(e164), true);
|
||||
verify(messagesManager).clear(existingUuid);
|
||||
verify(profilesManager).deleteAll(existingUuid);
|
||||
verify(messagesManager, times(2)).clear(existingUuid);
|
||||
verify(profilesManager, times(2)).deleteAll(existingUuid);
|
||||
verify(clientPresenceManager).disconnectAllPresencesForUuid(existingUuid);
|
||||
}
|
||||
|
||||
@@ -1018,7 +1035,7 @@ class AccountsManagerTest {
|
||||
final UUID recentlyDeletedUuid = UUID.randomUUID();
|
||||
|
||||
when(accounts.findRecentlyDeletedAccountIdentifier(anyString())).thenReturn(Optional.of(recentlyDeletedUuid));
|
||||
when(accounts.create(any(), any())).thenReturn(true);
|
||||
when(accounts.create(any(), any(), any())).thenReturn(true);
|
||||
|
||||
final String e164 = "+18005550123";
|
||||
final AccountAttributes attributes = new AccountAttributes(false, 1, 2, null, null, true, null);
|
||||
@@ -1027,6 +1044,7 @@ class AccountsManagerTest {
|
||||
|
||||
verify(accounts).create(
|
||||
argThat(account -> e164.equals(account.getNumber()) && recentlyDeletedUuid.equals(account.getUuid())),
|
||||
any(),
|
||||
any());
|
||||
|
||||
verifyNoInteractions(keysManager);
|
||||
|
||||
@@ -149,6 +149,7 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
experimentEnrollmentManager,
|
||||
mock(RegistrationRecoveryPasswordsManager.class),
|
||||
mock(Executor.class),
|
||||
mock(Executor.class),
|
||||
mock(Clock.class));
|
||||
}
|
||||
|
||||
@@ -312,7 +313,8 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
final Account account = AccountsHelper.createAccount(accountsManager, "+18005551111");
|
||||
|
||||
account.setUsernameHash(TestRandomUtil.nextBytes(16));
|
||||
accounts.create(account, ignored -> Collections.emptyList());
|
||||
accounts.create(account, ignored -> Collections.emptyList(),
|
||||
(ignoredAci, ignoredPni) -> CompletableFuture.completedFuture(null));
|
||||
|
||||
final UUID linkHandle = UUID.randomUUID();
|
||||
final byte[] encryptedUsername = TestRandomUtil.nextBytes(32);
|
||||
|
||||
@@ -209,6 +209,34 @@ class AccountsTest {
|
||||
assertThat(accounts.findRecentlyDeletedAccountIdentifier(account.getNumber())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStoreCleanupFailure() {
|
||||
final Account existingAccount = nextRandomAccount();
|
||||
createAccount(existingAccount);
|
||||
|
||||
verifyStoredState(existingAccount.getNumber(),
|
||||
existingAccount.getUuid(),
|
||||
existingAccount.getPhoneNumberIdentifier(),
|
||||
existingAccount.getUsernameHash().orElse(null),
|
||||
existingAccount,
|
||||
true);
|
||||
|
||||
final CompletionException completionException = assertThrows(CompletionException.class,
|
||||
() -> accounts.create(generateAccount(existingAccount.getNumber(), UUID.randomUUID(), UUID.randomUUID()),
|
||||
ignored -> Collections.emptyList(),
|
||||
(aci, pni) -> CompletableFuture.failedFuture(new RuntimeException("OH NO"))));
|
||||
|
||||
assertTrue(completionException.getCause() instanceof RuntimeException);
|
||||
|
||||
// If the existing account cleanup task failed, we should not overwrite the existing account record
|
||||
verifyStoredState(existingAccount.getNumber(),
|
||||
existingAccount.getUuid(),
|
||||
existingAccount.getPhoneNumberIdentifier(),
|
||||
existingAccount.getUsernameHash().orElse(null),
|
||||
existingAccount,
|
||||
true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStoreMulti() {
|
||||
final List<Device> devices = List.of(generateDevice(DEVICE_ID_1), generateDevice(DEVICE_ID_2));
|
||||
@@ -1113,7 +1141,8 @@ class AccountsTest {
|
||||
}
|
||||
|
||||
private boolean createAccount(final Account account) {
|
||||
return accounts.create(account, ignored -> Collections.emptyList());
|
||||
return accounts.create(account, ignored -> Collections.emptyList(),
|
||||
(ignoredAci, ignoredPni) -> CompletableFuture.completedFuture(null));
|
||||
}
|
||||
|
||||
private static Account nextRandomAccount() {
|
||||
|
||||
Reference in New Issue
Block a user