Introduce async account updaters

This commit is contained in:
Jon Chambers
2023-07-12 17:27:32 -04:00
committed by Jon Chambers
parent d17c7aaba6
commit 82ed783a2d
2 changed files with 149 additions and 6 deletions

View File

@@ -101,6 +101,15 @@ class AccountsManagerTest {
return null;
};
private static final Answer<CompletableFuture<Void>> ACCOUNT_UPDATE_ASYNC_ANSWER = invocation -> {
// it is implicit in the update() contract is that a successful call will
// result in an incremented version
final Account updatedAccount = invocation.getArgument(0, Account.class);
updatedAccount.setVersion(updatedAccount.getVersion() + 1);
return CompletableFuture.completedFuture(null);
};
@BeforeEach
void setup() throws InterruptedException {
accounts = mock(Accounts.class);
@@ -115,6 +124,11 @@ class AccountsManagerTest {
//noinspection unchecked
asyncCommands = mock(RedisAdvancedClusterAsyncCommands.class);
when(asyncCommands.del(any())).thenReturn(MockRedisFuture.completedFuture(0L));
when(asyncCommands.get(any())).thenReturn(MockRedisFuture.completedFuture(null));
when(asyncCommands.setex(any(), anyLong(), any())).thenReturn(MockRedisFuture.completedFuture("OK"));
when(accounts.updateAsync(any())).thenReturn(CompletableFuture.completedFuture(null));
doAnswer((Answer<Void>) invocation -> {
final Account account = invocation.getArgument(0, Account.class);
@@ -719,12 +733,6 @@ class AccountsManagerTest {
when(commands.get(eq("Account3::" + uuid))).thenReturn(null);
when(accounts.getByAccountIdentifier(uuid)).thenReturn(
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[16])));
doThrow(ContestedOptimisticLockException.class)
.doAnswer(ACCOUNT_UPDATE_ANSWER)
.when(accounts).update(any());
when(accounts.getByAccountIdentifier(uuid)).thenReturn(
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[16])));
doThrow(ContestedOptimisticLockException.class)
@@ -743,6 +751,33 @@ class AccountsManagerTest {
verifyNoMoreInteractions(accounts);
}
@Test
void testUpdateAsync_optimisticLockingFailure() {
UUID uuid = UUID.randomUUID();
UUID pni = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[16]);
when(asyncCommands.get(eq("Account3::" + uuid))).thenReturn(null);
when(accounts.getByAccountIdentifierAsync(uuid)).thenReturn(CompletableFuture.completedFuture(
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, pni, new ArrayList<>(), new byte[16]))));
when(accounts.updateAsync(any()))
.thenReturn(CompletableFuture.failedFuture(new ContestedOptimisticLockException()))
.thenAnswer(ACCOUNT_UPDATE_ASYNC_ANSWER);
final IdentityKey identityKey = new IdentityKey(Curve.generateKeyPair().getPublicKey());
account = accountsManager.updateAsync(account, a -> a.setIdentityKey(identityKey)).join();
assertEquals(1, account.getVersion());
assertEquals(identityKey, account.getIdentityKey());
verify(accounts, times(1)).getByAccountIdentifierAsync(uuid);
verify(accounts, times(2)).updateAsync(any());
verifyNoMoreInteractions(accounts);
}
@Test
void testUpdate_dynamoOptimisticLockingFailureDuringCreate() {
UUID uuid = UUID.randomUUID();
@@ -793,6 +828,39 @@ class AccountsManagerTest {
verify(unknownDeviceUpdater, never()).accept(any(Device.class));
}
@Test
void testUpdateDeviceAsync() {
final UUID uuid = UUID.randomUUID();
Account account = AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(), new byte[16]);
when(accounts.getByAccountIdentifierAsync(uuid)).thenReturn(CompletableFuture.completedFuture(
Optional.of(AccountsHelper.generateTestAccount("+14152222222", uuid, UUID.randomUUID(), new ArrayList<>(), new byte[16]))));
assertTrue(account.getDevices().isEmpty());
Device enabledDevice = new Device();
enabledDevice.setFetchesMessages(true);
enabledDevice.setSignedPreKey(KeysHelper.signedECPreKey(1, Curve.generateKeyPair()));
enabledDevice.setLastSeen(System.currentTimeMillis());
final long deviceId = account.getNextDeviceId();
enabledDevice.setId(deviceId);
account.addDevice(enabledDevice);
@SuppressWarnings("unchecked") Consumer<Device> deviceUpdater = mock(Consumer.class);
@SuppressWarnings("unchecked") Consumer<Device> unknownDeviceUpdater = mock(Consumer.class);
account = accountsManager.updateDeviceAsync(account, deviceId, deviceUpdater).join();
account = accountsManager.updateDeviceAsync(account, deviceId, d -> d.setName("deviceName")).join();
assertEquals("deviceName", account.getDevice(deviceId).orElseThrow().getName());
verify(deviceUpdater, times(1)).accept(any(Device.class));
accountsManager.updateDeviceAsync(account, account.getNextDeviceId(), unknownDeviceUpdater).join();
verify(unknownDeviceUpdater, never()).accept(any(Device.class));
}
@Test
void testCreateFreshAccount() throws InterruptedException {
when(accounts.create(any())).thenReturn(true);