mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 12:08:03 +01:00
Retire integration with legacy contact discovery system
This commit is contained in:
@@ -343,30 +343,6 @@ class DynamicConfigurationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseDirectoryReconciler() throws JsonProcessingException {
|
||||
{
|
||||
final String emptyConfigYaml = REQUIRED_CONFIG.concat("test: true");
|
||||
final DynamicConfiguration emptyConfig =
|
||||
DynamicConfigurationManager.parseConfiguration(emptyConfigYaml, DynamicConfiguration.class).orElseThrow();
|
||||
|
||||
assertThat(emptyConfig.getDirectoryReconcilerConfiguration().isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
{
|
||||
final String directoryReconcilerConfig = REQUIRED_CONFIG.concat("""
|
||||
directoryReconciler:
|
||||
enabled: false
|
||||
""");
|
||||
|
||||
DynamicDirectoryReconcilerConfiguration directoryReconcilerConfiguration =
|
||||
DynamicConfigurationManager.parseConfiguration(directoryReconcilerConfig, DynamicConfiguration.class).orElseThrow()
|
||||
.getDirectoryReconcilerConfiguration();
|
||||
|
||||
assertThat(directoryReconcilerConfiguration.isEnabled()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseTurnConfig() throws JsonProcessingException {
|
||||
{
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.sqs;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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 java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
|
||||
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue;
|
||||
import software.amazon.awssdk.services.sqs.model.SendMessageRequest;
|
||||
import software.amazon.awssdk.services.sqs.model.SendMessageResponse;
|
||||
|
||||
public class DirectoryQueueTest {
|
||||
|
||||
private SqsAsyncClient sqsAsyncClient;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
sqsAsyncClient = mock(SqsAsyncClient.class);
|
||||
|
||||
when(sqsAsyncClient.sendMessage(any(SendMessageRequest.class)))
|
||||
.thenReturn(CompletableFuture.completedFuture(SendMessageResponse.builder().build()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("argumentsForTestRefreshRegisteredUser")
|
||||
void testRefreshRegisteredUser(final boolean shouldBeVisibleInDirectory, final String expectedAction) {
|
||||
final DirectoryQueue directoryQueue = new DirectoryQueue(List.of("sqs://test"), sqsAsyncClient);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005556543");
|
||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||
when(account.shouldBeVisibleInDirectory()).thenReturn(shouldBeVisibleInDirectory);
|
||||
|
||||
directoryQueue.refreshAccount(account);
|
||||
|
||||
final ArgumentCaptor<SendMessageRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageRequest.class);
|
||||
verify(sqsAsyncClient).sendMessage(requestCaptor.capture());
|
||||
|
||||
assertEquals(MessageAttributeValue.builder().dataType("String").stringValue(expectedAction).build(),
|
||||
requestCaptor.getValue().messageAttributes().get("action"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> argumentsForTestRefreshRegisteredUser() {
|
||||
return Stream.of(
|
||||
Arguments.of(true, "add"),
|
||||
Arguments.of(false, "delete"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendMessageMultipleQueues() {
|
||||
final DirectoryQueue directoryQueue = new DirectoryQueue(List.of("sqs://first", "sqs://second"), sqsAsyncClient);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005556543");
|
||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||
when(account.shouldBeVisibleInDirectory()).thenReturn(true);
|
||||
|
||||
directoryQueue.refreshAccount(account);
|
||||
|
||||
final ArgumentCaptor<SendMessageRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageRequest.class);
|
||||
verify(sqsAsyncClient, times(2)).sendMessage(requestCaptor.capture());
|
||||
|
||||
for (final SendMessageRequest sendMessageRequest : requestCaptor.getAllValues()) {
|
||||
assertEquals(MessageAttributeValue.builder().dataType("String").stringValue("add").build(),
|
||||
sendMessageRequest.messageAttributes().get("action"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStop() {
|
||||
final CompletableFuture<SendMessageResponse> sendMessageFuture = new CompletableFuture<>();
|
||||
when(sqsAsyncClient.sendMessage(any(SendMessageRequest.class))).thenReturn(sendMessageFuture);
|
||||
|
||||
final DirectoryQueue directoryQueue = new DirectoryQueue(List.of("sqs://test"), sqsAsyncClient);
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getNumber()).thenReturn("+18005556543");
|
||||
when(account.getUuid()).thenReturn(UUID.randomUUID());
|
||||
when(account.shouldBeVisibleInDirectory()).thenReturn(true);
|
||||
|
||||
directoryQueue.refreshAccount(account);
|
||||
|
||||
final CompletableFuture<Boolean> stopFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
directoryQueue.stop();
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
assertThrows(TimeoutException.class, () -> stopFuture.get(1, TimeUnit.SECONDS),
|
||||
"Directory queue should not finish shutting down until all outstanding requests are resolved");
|
||||
|
||||
sendMessageFuture.complete(SendMessageResponse.builder().build());
|
||||
assertTrue(stopFuture.join());
|
||||
}
|
||||
}
|
||||
@@ -33,15 +33,10 @@ import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
|
||||
class AccountsManagerChangeNumberIntegrationTest {
|
||||
|
||||
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 int SCAN_PAGE_SIZE = 1;
|
||||
|
||||
@RegisterExtension
|
||||
@@ -82,8 +77,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
SCAN_PAGE_SIZE);
|
||||
|
||||
deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||
Tables.DELETED_ACCOUNTS.tableName(),
|
||||
Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
|
||||
Tables.DELETED_ACCOUNTS.tableName());
|
||||
|
||||
final DeletedAccountsManager deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
|
||||
DYNAMO_DB_EXTENSION.getLegacyDynamoClient(),
|
||||
@@ -108,7 +102,6 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
deletedAccountsManager,
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ProfilesManager.class),
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.JsonHelpers;
|
||||
@@ -111,7 +110,6 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
phoneNumberIdentifiers,
|
||||
RedisClusterHelper.builder().stringCommands(commands).build(),
|
||||
deletedAccountsManager,
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ProfilesManager.class),
|
||||
|
||||
@@ -59,7 +59,6 @@ import org.whispersystems.textsecuregcm.push.ClientPresenceManager;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Device.DeviceCapabilities;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.DevicesHelper;
|
||||
@@ -73,7 +72,6 @@ class AccountsManagerTest {
|
||||
|
||||
private Accounts accounts;
|
||||
private DeletedAccountsManager deletedAccountsManager;
|
||||
private DirectoryQueue directoryQueue;
|
||||
private Keys keys;
|
||||
private MessagesManager messagesManager;
|
||||
private ProfilesManager profilesManager;
|
||||
@@ -96,7 +94,6 @@ class AccountsManagerTest {
|
||||
void setup() throws InterruptedException {
|
||||
accounts = mock(Accounts.class);
|
||||
deletedAccountsManager = mock(DeletedAccountsManager.class);
|
||||
directoryQueue = mock(DirectoryQueue.class);
|
||||
keys = mock(Keys.class);
|
||||
messagesManager = mock(MessagesManager.class);
|
||||
profilesManager = mock(ProfilesManager.class);
|
||||
@@ -153,7 +150,6 @@ class AccountsManagerTest {
|
||||
phoneNumberIdentifiers,
|
||||
RedisClusterHelper.builder().stringCommands(commands).build(),
|
||||
deletedAccountsManager,
|
||||
directoryQueue,
|
||||
keys,
|
||||
messagesManager,
|
||||
profilesManager,
|
||||
@@ -598,10 +594,6 @@ class AccountsManagerTest {
|
||||
final Account account = accountsManager.create("+18005550123", "password", null, attributes, new ArrayList<>());
|
||||
|
||||
assertEquals(discoverable, account.isDiscoverableByPhoneNumber());
|
||||
|
||||
if (!discoverable) {
|
||||
verify(directoryQueue).deleteAccount(account);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@@ -615,32 +607,6 @@ class AccountsManagerTest {
|
||||
assertEquals(hasStorage, account.isStorageSupported());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testUpdateDirectoryQueue(final boolean visibleBeforeUpdate, final boolean visibleAfterUpdate,
|
||||
final boolean expectRefresh) {
|
||||
final Account account = AccountsHelper.generateTestAccount("+14152222222", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
|
||||
// this sets up the appropriate result for Account#shouldBeVisibleInDirectory
|
||||
final Device device = generateTestDevice(0);
|
||||
account.addDevice(device);
|
||||
account.setDiscoverableByPhoneNumber(visibleBeforeUpdate);
|
||||
|
||||
final Account updatedAccount = accountsManager.update(account,
|
||||
a -> a.setDiscoverableByPhoneNumber(visibleAfterUpdate));
|
||||
|
||||
verify(directoryQueue, times(expectRefresh ? 1 : 0)).refreshAccount(updatedAccount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> testUpdateDirectoryQueue() {
|
||||
return Stream.of(
|
||||
Arguments.of(false, false, false),
|
||||
Arguments.of(true, true, false),
|
||||
Arguments.of(false, true, true),
|
||||
Arguments.of(true, false, true));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testUpdateDeviceLastSeen(final boolean expectUpdate, final long initialLastSeen, final long updatedLastSeen) {
|
||||
@@ -680,7 +646,6 @@ class AccountsManagerTest {
|
||||
|
||||
assertTrue(phoneNumberIdentifiersByE164.containsKey(targetNumber));
|
||||
|
||||
verify(directoryQueue).changePhoneNumber(argThat(a -> a.getUuid().equals(uuid)), eq(originalNumber), eq(targetNumber));
|
||||
verify(keys).delete(originalPni);
|
||||
verify(keys).delete(phoneNumberIdentifiersByE164.get(targetNumber));
|
||||
}
|
||||
@@ -694,7 +659,6 @@ class AccountsManagerTest {
|
||||
|
||||
assertEquals(number, account.getNumber());
|
||||
verify(deletedAccountsManager, never()).lockAndPut(anyString(), anyString(), any());
|
||||
verify(directoryQueue, never()).changePhoneNumber(any(), any(), any());
|
||||
verify(keys, never()).delete(any());
|
||||
}
|
||||
|
||||
@@ -736,8 +700,6 @@ class AccountsManagerTest {
|
||||
|
||||
assertTrue(phoneNumberIdentifiersByE164.containsKey(targetNumber));
|
||||
|
||||
verify(directoryQueue).changePhoneNumber(argThat(a -> a.getUuid().equals(uuid)), eq(originalNumber), eq(targetNumber));
|
||||
verify(directoryQueue).deleteAccount(existingAccount);
|
||||
verify(keys).delete(originalPni);
|
||||
verify(keys).delete(targetPni);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
|
||||
import org.whispersystems.textsecuregcm.securebackup.SecureBackupClient;
|
||||
import org.whispersystems.textsecuregcm.securestorage.SecureStorageClient;
|
||||
import org.whispersystems.textsecuregcm.securevaluerecovery.SecureValueRecovery2Client;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
|
||||
@@ -114,7 +113,6 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
phoneNumberIdentifiers,
|
||||
CACHE_CLUSTER_EXTENSION.getRedisCluster(),
|
||||
deletedAccountsManager,
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ProfilesManager.class),
|
||||
|
||||
@@ -5,21 +5,14 @@
|
||||
|
||||
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.assertThrows;
|
||||
|
||||
import java.lang.Thread.State;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
|
||||
class DeletedAccountsManagerTest {
|
||||
@@ -34,8 +27,7 @@ class DeletedAccountsManagerTest {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||
Tables.DELETED_ACCOUNTS.tableName(),
|
||||
Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
|
||||
Tables.DELETED_ACCOUNTS.tableName());
|
||||
|
||||
deletedAccountsManager = new DeletedAccountsManager(deletedAccounts,
|
||||
DYNAMO_DB_EXTENSION.getLegacyDynamoClient(),
|
||||
@@ -47,7 +39,7 @@ class DeletedAccountsManagerTest {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final String e164 = "+18005551234";
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
deletedAccountsManager.lockAndTake(e164, maybeUuid -> assertEquals(Optional.of(uuid), maybeUuid));
|
||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(e164));
|
||||
}
|
||||
@@ -57,7 +49,7 @@ class DeletedAccountsManagerTest {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final String e164 = "+18005551234";
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
|
||||
assertThrows(RuntimeException.class, () -> deletedAccountsManager.lockAndTake(e164, maybeUuid -> {
|
||||
assertEquals(Optional.of(uuid), maybeUuid);
|
||||
@@ -66,73 +58,4 @@ class DeletedAccountsManagerTest {
|
||||
|
||||
assertEquals(Optional.of(uuid), deletedAccounts.findUuid(e164));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReconciliationLockContention() throws ChunkProcessingFailedException {
|
||||
|
||||
final UUID[] uuids = new UUID[3];
|
||||
final String[] e164s = new String[uuids.length];
|
||||
|
||||
for (int i = 0; i < uuids.length; i++) {
|
||||
uuids[i] = UUID.randomUUID();
|
||||
e164s[i] = String.format("+1800555%04d", i);
|
||||
}
|
||||
|
||||
final Map<String, UUID> expectedReconciledAccounts = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < uuids.length; i++) {
|
||||
deletedAccounts.put(uuids[i], e164s[i], true);
|
||||
expectedReconciledAccounts.put(e164s[i], uuids[i]);
|
||||
}
|
||||
|
||||
final UUID replacedUUID = UUID.randomUUID();
|
||||
final Map<String, UUID> reconciledAccounts = new HashMap<>();
|
||||
|
||||
final Thread putThread = new Thread(() -> {
|
||||
try {
|
||||
deletedAccountsManager.lockAndPut(e164s[0], () -> replacedUUID);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
},
|
||||
getClass().getSimpleName() + "-put");
|
||||
|
||||
final Thread reconcileThread = new Thread(() -> {
|
||||
try {
|
||||
deletedAccountsManager.lockAndReconcileAccounts(uuids.length, deletedAccounts -> {
|
||||
// We hold the lock for the first account, so a thread trying to operate on that first count should block
|
||||
// waiting for the lock.
|
||||
putThread.start();
|
||||
|
||||
// Make sure the other thread really does actually block at some point
|
||||
while (putThread.getState() != State.TIMED_WAITING) {
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
deletedAccounts.forEach(pair -> reconciledAccounts.put(pair.second(), pair.first()));
|
||||
return reconciledAccounts.keySet();
|
||||
});
|
||||
} catch (ChunkProcessingFailedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}, getClass().getSimpleName() + "-reconcile");
|
||||
|
||||
reconcileThread.start();
|
||||
|
||||
assertDoesNotThrow((Executable) reconcileThread::join);
|
||||
assertDoesNotThrow((Executable) putThread::join);
|
||||
|
||||
assertEquals(expectedReconciledAccounts, reconciledAccounts);
|
||||
|
||||
// The "put" thread should have completed after the reconciliation thread wrapped up. We can verify that's true by
|
||||
// reconciling again; the updated account (and only that account) should appear in the "needs reconciliation" list.
|
||||
deletedAccountsManager.lockAndReconcileAccounts(uuids.length, deletedAccounts -> {
|
||||
assertEquals(1, deletedAccounts.size());
|
||||
assertEquals(replacedUUID, deletedAccounts.get(0).first());
|
||||
assertEquals(e164s[0], deletedAccounts.get(0).second());
|
||||
|
||||
return List.of(deletedAccounts.get(0).second());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,20 +5,13 @@
|
||||
package org.whispersystems.textsecuregcm.storage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Indexes;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamoDbExtensionSchema.Tables;
|
||||
import org.whispersystems.textsecuregcm.util.Pair;
|
||||
|
||||
class DeletedAccountsTest {
|
||||
|
||||
@@ -29,41 +22,7 @@ class DeletedAccountsTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(),
|
||||
Tables.DELETED_ACCOUNTS.tableName(),
|
||||
Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPutList() {
|
||||
UUID firstUuid = UUID.randomUUID();
|
||||
UUID secondUuid = UUID.randomUUID();
|
||||
UUID thirdUuid = UUID.randomUUID();
|
||||
|
||||
String firstNumber = "+14152221234";
|
||||
String secondNumber = "+14152225678";
|
||||
String thirdNumber = "+14159998765";
|
||||
|
||||
assertTrue(deletedAccounts.listAccountsToReconcile(1).isEmpty());
|
||||
|
||||
deletedAccounts.put(firstUuid, firstNumber, true);
|
||||
deletedAccounts.put(secondUuid, secondNumber, true);
|
||||
deletedAccounts.put(thirdUuid, thirdNumber, true);
|
||||
|
||||
assertEquals(1, deletedAccounts.listAccountsToReconcile(1).size());
|
||||
|
||||
assertTrue(deletedAccounts.listAccountsToReconcile(10).containsAll(
|
||||
List.of(
|
||||
new Pair<>(firstUuid, firstNumber),
|
||||
new Pair<>(secondUuid, secondNumber))));
|
||||
|
||||
deletedAccounts.markReconciled(List.of(firstNumber, secondNumber));
|
||||
|
||||
assertEquals(List.of(new Pair<>(thirdUuid, thirdNumber)), deletedAccounts.listAccountsToReconcile(10));
|
||||
|
||||
deletedAccounts.markReconciled(List.of(thirdNumber));
|
||||
|
||||
assertTrue(deletedAccounts.listAccountsToReconcile(1).isEmpty());
|
||||
deletedAccounts = new DeletedAccounts(DYNAMO_DB_EXTENSION.getDynamoDbClient(), Tables.DELETED_ACCOUNTS.tableName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -73,7 +32,7 @@ class DeletedAccountsTest {
|
||||
|
||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(e164));
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
|
||||
assertEquals(Optional.of(uuid), deletedAccounts.findUuid(e164));
|
||||
}
|
||||
@@ -85,7 +44,7 @@ class DeletedAccountsTest {
|
||||
|
||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(e164));
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
|
||||
assertEquals(Optional.of(uuid), deletedAccounts.findUuid(e164));
|
||||
|
||||
@@ -94,45 +53,6 @@ class DeletedAccountsTest {
|
||||
assertEquals(Optional.empty(), deletedAccounts.findUuid(e164));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountsNeedingReconciliation() {
|
||||
final UUID firstUuid = UUID.randomUUID();
|
||||
final UUID secondUuid = UUID.randomUUID();
|
||||
|
||||
final String firstNumber = "+14152221234";
|
||||
final String secondNumber = "+14152225678";
|
||||
final String thirdNumber = "+14159998765";
|
||||
|
||||
assertEquals(Collections.emptySet(),
|
||||
deletedAccounts.getAccountsNeedingReconciliation(List.of(firstNumber, secondNumber, thirdNumber)));
|
||||
|
||||
deletedAccounts.put(firstUuid, firstNumber, true);
|
||||
deletedAccounts.put(secondUuid, secondNumber, true);
|
||||
|
||||
assertEquals(Set.of(firstNumber, secondNumber),
|
||||
deletedAccounts.getAccountsNeedingReconciliation(List.of(firstNumber, secondNumber, thirdNumber)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAccountsNeedingReconciliationLargeBatch() {
|
||||
final int itemCount = (DeletedAccounts.GET_BATCH_SIZE * 3) + 1;
|
||||
|
||||
final Set<String> expectedAccountsNeedingReconciliation = new HashSet<>(itemCount);
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
final String e164 = String.format("+18000555%04d", i);
|
||||
|
||||
deletedAccounts.put(UUID.randomUUID(), e164, true);
|
||||
expectedAccountsNeedingReconciliation.add(e164);
|
||||
}
|
||||
|
||||
final Set<String> accountsNeedingReconciliation =
|
||||
deletedAccounts.getAccountsNeedingReconciliation(expectedAccountsNeedingReconciliation);
|
||||
|
||||
assertEquals(expectedAccountsNeedingReconciliation, accountsNeedingReconciliation);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testFindE164() {
|
||||
assertEquals(Optional.empty(), deletedAccounts.findE164(UUID.randomUUID()));
|
||||
@@ -140,7 +60,7 @@ class DeletedAccountsTest {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final String e164 = "+18005551234";
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
|
||||
assertEquals(Optional.of(e164), deletedAccounts.findE164(uuid));
|
||||
}
|
||||
@@ -153,7 +73,7 @@ class DeletedAccountsTest {
|
||||
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
|
||||
deletedAccounts.put(uuid, e164, true);
|
||||
deletedAccounts.put(uuid, e164);
|
||||
|
||||
assertEquals(Optional.of(uuid), deletedAccounts.findUuid(e164));
|
||||
}
|
||||
|
||||
@@ -18,20 +18,6 @@ import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
public final class DynamoDbExtensionSchema {
|
||||
|
||||
public enum Indexes {
|
||||
|
||||
DELETED_ACCOUNTS_NEEDS_RECONCILIATION("needs_reconciliation_test");
|
||||
|
||||
private final String name;
|
||||
|
||||
public String indexName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
Indexes(final String name) { this.name = name; }
|
||||
|
||||
}
|
||||
|
||||
public enum Tables implements DynamoDbExtension.TableSchema {
|
||||
|
||||
ACCOUNTS("accounts_test",
|
||||
@@ -50,22 +36,11 @@ public final class DynamoDbExtensionSchema {
|
||||
AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.KEY_ACCOUNT_E164)
|
||||
.attributeType(ScalarAttributeType.S).build(),
|
||||
AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.ATTR_NEEDS_CDS_RECONCILIATION)
|
||||
.attributeType(ScalarAttributeType.N)
|
||||
.build(),
|
||||
AttributeDefinition.builder()
|
||||
.attributeName(DeletedAccounts.ATTR_ACCOUNT_UUID)
|
||||
.attributeType(ScalarAttributeType.B)
|
||||
.build()),
|
||||
List.of(
|
||||
GlobalSecondaryIndex.builder()
|
||||
.indexName(Indexes.DELETED_ACCOUNTS_NEEDS_RECONCILIATION.indexName())
|
||||
.keySchema(KeySchemaElement.builder().attributeName(DeletedAccounts.KEY_ACCOUNT_E164).keyType(KeyType.HASH).build(),
|
||||
KeySchemaElement.builder().attributeName(DeletedAccounts.ATTR_NEEDS_CDS_RECONCILIATION).keyType(KeyType.RANGE).build())
|
||||
.projection(Projection.builder().projectionType(ProjectionType.INCLUDE).nonKeyAttributes(DeletedAccounts.ATTR_ACCOUNT_UUID).build())
|
||||
.provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(10L).writeCapacityUnits(10L).build())
|
||||
.build(),
|
||||
GlobalSecondaryIndex.builder()
|
||||
.indexName(DeletedAccounts.UUID_TO_E164_INDEX_NAME)
|
||||
.keySchema(
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
|
||||
import io.dropwizard.testing.junit5.ResourceExtension;
|
||||
import java.util.Collections;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status.Family;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.DisabledPermittedAuthenticatedAccount;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.ExternalServiceCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
|
||||
@ExtendWith(DropwizardExtensionsSupport.class)
|
||||
class DirectoryControllerTest {
|
||||
|
||||
private static final ExternalServiceCredentialsGenerator directoryCredentialsGenerator = mock(ExternalServiceCredentialsGenerator.class);
|
||||
private static final ExternalServiceCredentials validCredentials = new ExternalServiceCredentials("username", "password");
|
||||
|
||||
private static final ResourceExtension resources = ResourceExtension.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new PolymorphicAuthValueFactoryProvider.Binder<>(
|
||||
ImmutableSet.of(AuthenticatedAccount.class, DisabledPermittedAuthenticatedAccount.class)))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new DirectoryController(directoryCredentialsGenerator))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(directoryCredentialsGenerator.generateFor(eq(AuthHelper.VALID_NUMBER))).thenReturn(validCredentials);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeedbackOk() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v3/ok")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json("{\"reason\": \"test reason\"}"));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAuthToken() {
|
||||
ExternalServiceCredentials token =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/auth")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.get(ExternalServiceCredentials.class);
|
||||
assertThat(token.username()).isEqualTo(validCredentials.username());
|
||||
assertThat(token.password()).isEqualTo(validCredentials.password());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDisabledGetAuthToken() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/auth")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.DISABLED_UUID, AuthHelper.DISABLED_PASSWORD))
|
||||
.get();
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testContactIntersection() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/tokens/")
|
||||
.request()
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_UUID,
|
||||
AuthHelper.VALID_PASSWORD))
|
||||
.header(HttpHeaders.X_FORWARDED_FOR, "192.168.1.1, 1.1.1.1")
|
||||
.put(Entity.entity(Collections.emptyMap(), MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(429);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2021 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationRequest.User;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationResponse;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
|
||||
class DirectoryReconcilerTest {
|
||||
|
||||
private static final UUID VALID_UUID = UUID.randomUUID();
|
||||
private static final String VALID_NUMBER = "+14152222222";
|
||||
private static final UUID UNDISCOVERABLE_UUID = UUID.randomUUID();
|
||||
private static final String UNDISCOVERABLE_NUMBER = "+14153333333";
|
||||
|
||||
private final Account visibleAccount = mock(Account.class);
|
||||
private final Account undiscoverableAccount = mock(Account.class);
|
||||
private final DirectoryReconciliationClient reconciliationClient = mock(DirectoryReconciliationClient.class);
|
||||
private final DynamicConfigurationManager dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
|
||||
private final DirectoryReconciler directoryReconciler = new DirectoryReconciler("test", reconciliationClient,
|
||||
dynamicConfigurationManager);
|
||||
|
||||
private final DirectoryReconciliationResponse successResponse = new DirectoryReconciliationResponse(
|
||||
DirectoryReconciliationResponse.Status.OK);
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
when(dynamicConfigurationManager.getConfiguration()).thenReturn(new DynamicConfiguration());
|
||||
|
||||
when(visibleAccount.getUuid()).thenReturn(VALID_UUID);
|
||||
when(visibleAccount.getNumber()).thenReturn(VALID_NUMBER);
|
||||
when(visibleAccount.shouldBeVisibleInDirectory()).thenReturn(true);
|
||||
when(undiscoverableAccount.getUuid()).thenReturn(UNDISCOVERABLE_UUID);
|
||||
when(undiscoverableAccount.getNumber()).thenReturn(UNDISCOVERABLE_NUMBER);
|
||||
when(undiscoverableAccount.shouldBeVisibleInDirectory()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCrawlChunkValid() throws AccountDatabaseCrawlerRestartException {
|
||||
|
||||
when(reconciliationClient.add(any())).thenReturn(successResponse);
|
||||
when(reconciliationClient.delete(any())).thenReturn(successResponse);
|
||||
|
||||
directoryReconciler.timeAndProcessCrawlChunk(Optional.of(VALID_UUID),
|
||||
Arrays.asList(visibleAccount, undiscoverableAccount));
|
||||
|
||||
ArgumentCaptor<DirectoryReconciliationRequest> chunkRequest = ArgumentCaptor.forClass(
|
||||
DirectoryReconciliationRequest.class);
|
||||
verify(reconciliationClient, times(1)).add(chunkRequest.capture());
|
||||
|
||||
assertThat(chunkRequest.getValue().getUsers()).isEqualTo(List.of(new User(VALID_UUID, VALID_NUMBER)));
|
||||
|
||||
ArgumentCaptor<DirectoryReconciliationRequest> deletesRequest = ArgumentCaptor.forClass(
|
||||
DirectoryReconciliationRequest.class);
|
||||
verify(reconciliationClient, times(1)).delete(deletesRequest.capture());
|
||||
|
||||
assertThat(deletesRequest.getValue().getUsers()).isEqualTo(
|
||||
List.of(new User(UNDISCOVERABLE_UUID, UNDISCOVERABLE_NUMBER)));
|
||||
|
||||
verifyNoMoreInteractions(reconciliationClient);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user