mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 10:58:05 +01:00
Add reserve/confirm for usernames
This commit is contained in:
committed by
ravi-signal
parent
98c8dc05f1
commit
4032ddd4fd
@@ -193,7 +193,7 @@ class AccountsManagerChangeNumberIntegrationTest {
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ReservedUsernames.class),
|
||||
mock(ProhibitedUsernames.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
secureStorageClient,
|
||||
|
||||
@@ -160,7 +160,7 @@ class AccountsManagerConcurrentModificationIntegrationTest {
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ReservedUsernames.class),
|
||||
mock(ProhibitedUsernames.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
|
||||
@@ -71,7 +71,7 @@ class AccountsManagerTest {
|
||||
private Keys keys;
|
||||
private MessagesManager messagesManager;
|
||||
private ProfilesManager profilesManager;
|
||||
private ReservedUsernames reservedUsernames;
|
||||
private ProhibitedUsernames prohibitedUsernames;
|
||||
private ExperimentEnrollmentManager enrollmentManager;
|
||||
|
||||
private Map<String, UUID> phoneNumberIdentifiersByE164;
|
||||
@@ -87,6 +87,8 @@ class AccountsManagerTest {
|
||||
return null;
|
||||
};
|
||||
|
||||
private static final UUID RESERVATION_TOKEN = UUID.randomUUID();
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws InterruptedException {
|
||||
accounts = mock(Accounts.class);
|
||||
@@ -95,7 +97,7 @@ class AccountsManagerTest {
|
||||
keys = mock(Keys.class);
|
||||
messagesManager = mock(MessagesManager.class);
|
||||
profilesManager = mock(ProfilesManager.class);
|
||||
reservedUsernames = mock(ReservedUsernames.class);
|
||||
prohibitedUsernames = mock(ProhibitedUsernames.class);
|
||||
|
||||
//noinspection unchecked
|
||||
commands = mock(RedisAdvancedClusterCommands.class);
|
||||
@@ -149,7 +151,7 @@ class AccountsManagerTest {
|
||||
directoryQueue,
|
||||
keys,
|
||||
messagesManager,
|
||||
reservedUsernames,
|
||||
prohibitedUsernames,
|
||||
profilesManager,
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
storageClient,
|
||||
@@ -737,6 +739,65 @@ class AccountsManagerTest {
|
||||
verify(accounts).setUsername(eq(account), startsWith(nickname));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReserveUsername() throws UsernameNotAvailableException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String nickname = "beethoven";
|
||||
accountsManager.reserveUsername(account, nickname);
|
||||
verify(accounts).reserveUsername(eq(account), startsWith(nickname), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedUsername() throws UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String reserved = "scooby#1234";
|
||||
setReservationHash(account, reserved);
|
||||
when(accounts.usernameAvailable(eq(Optional.of(RESERVATION_TOKEN)), eq(reserved))).thenReturn(true);
|
||||
accountsManager.confirmReservedUsername(account, reserved, RESERVATION_TOKEN);
|
||||
verify(accounts).confirmUsername(eq(account), eq(reserved), eq(RESERVATION_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedHashNameMismatch() {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
setReservationHash(account, "pluto#1234");
|
||||
when(accounts.usernameAvailable(eq(Optional.of(RESERVATION_TOKEN)), eq("pluto#1234"))).thenReturn(true);
|
||||
assertThrows(UsernameReservationNotFoundException.class,
|
||||
() -> accountsManager.confirmReservedUsername(account, "goofy#1234", RESERVATION_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedHashAciMismatch() {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String reserved = "toto#1234";
|
||||
account.setReservedUsernameHash(Accounts.reservedUsernameHash(UUID.randomUUID(), reserved));
|
||||
when(accounts.usernameAvailable(eq(Optional.of(RESERVATION_TOKEN)), eq(reserved))).thenReturn(true);
|
||||
assertThrows(UsernameReservationNotFoundException.class,
|
||||
() -> accountsManager.confirmReservedUsername(account, reserved, RESERVATION_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedLapsed() {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String reserved = "porkchop#1234";
|
||||
// name was reserved, but the reservation lapsed and another account took it
|
||||
setReservationHash(account, reserved);
|
||||
when(accounts.usernameAvailable(eq(Optional.of(RESERVATION_TOKEN)), eq(reserved))).thenReturn(false);
|
||||
assertThrows(UsernameNotAvailableException.class, () -> accountsManager.confirmReservedUsername(account, reserved, RESERVATION_TOKEN));
|
||||
verify(accounts, never()).confirmUsername(any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedRetry() throws UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String username = "santaslittlehelper#1234";
|
||||
account.setUsername(username);
|
||||
|
||||
// reserved username already set, should be treated as a replay
|
||||
accountsManager.confirmReservedUsername(account, username, RESERVATION_TOKEN);
|
||||
verifyNoInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameSameUsername() {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
@@ -761,7 +822,29 @@ class AccountsManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsernameExpandDiscriminator() throws UsernameNotAvailableException {
|
||||
void testReserveUsernameReroll() throws UsernameNotAvailableException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String nickname = "clifford";
|
||||
final String username = nickname + "#ZZZ";
|
||||
account.setUsername(username);
|
||||
|
||||
// given the correct old username, should reroll discriminator even if the nick matches
|
||||
accountsManager.reserveUsername(account, nickname);
|
||||
verify(accounts).reserveUsername(eq(account), and(startsWith(nickname), not(eq(username))), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetReservedUsernameWithNoReservation() {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(),
|
||||
new ArrayList<>(), new byte[16]);
|
||||
assertThrows(UsernameReservationNotFoundException.class,
|
||||
() -> accountsManager.confirmReservedUsername(account, "laika#1234", RESERVATION_TOKEN));
|
||||
verify(accounts, never()).confirmUsername(any(), any(), any());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void testUsernameExpandDiscriminator(boolean reserve) throws UsernameNotAvailableException {
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
final String nickname = "test";
|
||||
|
||||
@@ -775,8 +858,14 @@ class AccountsManagerTest {
|
||||
when(accounts.usernameAvailable(any())).thenReturn(false);
|
||||
when(accounts.usernameAvailable(argThat(isWide))).thenReturn(true);
|
||||
|
||||
accountsManager.setUsername(account, nickname, null);
|
||||
verify(accounts).setUsername(eq(account), and(startsWith(nickname), argThat(isWide)));
|
||||
if (reserve) {
|
||||
accountsManager.reserveUsername(account, nickname);
|
||||
verify(accounts).reserveUsername(eq(account), and(startsWith(nickname), argThat(isWide)), any());
|
||||
|
||||
} else {
|
||||
accountsManager.setUsername(account, nickname, null);
|
||||
verify(accounts).setUsername(eq(account), and(startsWith(nickname), argThat(isWide)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -801,7 +890,7 @@ class AccountsManagerTest {
|
||||
@Test
|
||||
void testSetUsernameReserved() {
|
||||
final String nickname = "reserved";
|
||||
when(reservedUsernames.isReserved(eq(nickname), any())).thenReturn(true);
|
||||
when(prohibitedUsernames.isProhibited(eq(nickname), any())).thenReturn(true);
|
||||
|
||||
final Account account = AccountsHelper.generateTestAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID(), new ArrayList<>(), new byte[16]);
|
||||
|
||||
@@ -823,6 +912,10 @@ class AccountsManagerTest {
|
||||
assertThrows(UsernameNotAvailableException.class, () -> accountsManager.setUsername(account, "n00bkiller", null));
|
||||
}
|
||||
|
||||
private void setReservationHash(final Account account, final String reservedUsername) {
|
||||
account.setReservedUsernameHash(Accounts.reservedUsernameHash(account.getUuid(), reservedUsername));
|
||||
}
|
||||
|
||||
private static Device generateTestDevice(final long lastSeen) {
|
||||
final Device device = new Device();
|
||||
device.setId(Device.MASTER_ID);
|
||||
|
||||
@@ -8,6 +8,8 @@ package org.whispersystems.textsecuregcm.storage;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||
@@ -22,6 +24,8 @@ import org.whispersystems.textsecuregcm.util.AttributeValues;
|
||||
import org.whispersystems.textsecuregcm.util.UsernameGenerator;
|
||||
import software.amazon.awssdk.services.dynamodb.model.*;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -110,7 +114,11 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
.build();
|
||||
|
||||
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().createTable(createPhoneNumberIdentifierTableRequest);
|
||||
buildAccountsManager(1, 2, 10);
|
||||
}
|
||||
|
||||
private void buildAccountsManager(final int initialWidth, int discriminatorMaxWidth, int attemptsPerWidth)
|
||||
throws InterruptedException {
|
||||
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
|
||||
mock(DynamicConfigurationManager.class);
|
||||
|
||||
@@ -127,6 +135,8 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
USERNAMES_TABLE_NAME,
|
||||
SCAN_PAGE_SIZE));
|
||||
|
||||
usernameGenerator = new UsernameGenerator(initialWidth, discriminatorMaxWidth, attemptsPerWidth,
|
||||
Duration.ofDays(1));
|
||||
final DeletedAccountsManager deletedAccountsManager = mock(DeletedAccountsManager.class);
|
||||
doAnswer((final InvocationOnMock invocationOnMock) -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -141,8 +151,6 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
final ExperimentEnrollmentManager experimentEnrollmentManager = mock(ExperimentEnrollmentManager.class);
|
||||
when(experimentEnrollmentManager.isEnrolled(any(UUID.class), eq(AccountsManager.USERNAME_EXPERIMENT_NAME)))
|
||||
.thenReturn(true);
|
||||
|
||||
usernameGenerator = new UsernameGenerator(1, 2, 10);
|
||||
accountsManager = new AccountsManager(
|
||||
accounts,
|
||||
phoneNumberIdentifiers,
|
||||
@@ -151,7 +159,7 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
mock(DirectoryQueue.class),
|
||||
mock(Keys.class),
|
||||
mock(MessagesManager.class),
|
||||
mock(ReservedUsernames.class),
|
||||
mock(ProhibitedUsernames.class),
|
||||
mock(ProfilesManager.class),
|
||||
mock(StoredVerificationCodeManager.class),
|
||||
mock(SecureStorageClient.class),
|
||||
@@ -190,19 +198,32 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
assertThat(accountsManager.getByAccountIdentifier(account.getUuid()).orElseThrow().getUsername()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoUsernames() throws InterruptedException {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void testNoUsernames(boolean reserve) throws InterruptedException {
|
||||
Account account = accountsManager.create("+18005551111", "password", null, new AccountAttributes(),
|
||||
new ArrayList<>());
|
||||
for (int i = 1; i <= 99; i++) {
|
||||
final Map<String, AttributeValue> item = new HashMap<>(Map.of(
|
||||
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()),
|
||||
Accounts.ATTR_USERNAME, AttributeValues.fromString(usernameGenerator.fromParts("n00bkiller", i))));
|
||||
// half of these are taken usernames, half are only reservations (have a TTL)
|
||||
if (i % 2 == 0) {
|
||||
item.put(Accounts.ATTR_TTL,
|
||||
AttributeValues.fromLong(Instant.now().plus(Duration.ofMinutes(1)).getEpochSecond()));
|
||||
}
|
||||
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
|
||||
.tableName(USERNAMES_TABLE_NAME)
|
||||
.item(Map.of(
|
||||
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()),
|
||||
Accounts.ATTR_USERNAME, AttributeValues.fromString(usernameGenerator.fromParts("n00bkiller", i))))
|
||||
.item(item)
|
||||
.build());
|
||||
}
|
||||
assertThrows(UsernameNotAvailableException.class, () -> accountsManager.setUsername(account, "n00bkiller", null));
|
||||
assertThrows(UsernameNotAvailableException.class, () -> {
|
||||
if (reserve) {
|
||||
accountsManager.reserveUsername(account, "n00bkiller");
|
||||
} else {
|
||||
accountsManager.setUsername(account, "n00bkiller", null);
|
||||
}
|
||||
});
|
||||
assertThat(accountsManager.getByAccountIdentifier(account.getUuid()).orElseThrow().getUsername()).isEmpty();
|
||||
}
|
||||
|
||||
@@ -237,4 +258,112 @@ class AccountsManagerUsernameIntegrationTest {
|
||||
verify(accounts, times(1)).usernameAvailable(argThat(un -> discriminator(un) >= 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReserveSetClear()
|
||||
throws InterruptedException, UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
Account account = accountsManager.create("+18005551111", "password", null, new AccountAttributes(),
|
||||
new ArrayList<>());
|
||||
AccountsManager.UsernameReservation reservation = accountsManager.reserveUsername(account, "n00bkiller");
|
||||
account = reservation.account();
|
||||
assertThat(account.getReservedUsernameHash()).isPresent();
|
||||
assertThat(reservation.reservedUsername()).startsWith("n00bkiller");
|
||||
int discriminator = discriminator(reservation.reservedUsername());
|
||||
assertThat(discriminator).isGreaterThan(0).isLessThan(10);
|
||||
assertThat(accountsManager.getByUsername(reservation.reservedUsername())).isEmpty();
|
||||
|
||||
account = accountsManager.confirmReservedUsername(
|
||||
account,
|
||||
reservation.reservedUsername(),
|
||||
reservation.reservationToken());
|
||||
|
||||
assertThat(account.getUsername().get()).startsWith("n00bkiller");
|
||||
assertThat(accountsManager.getByUsername(account.getUsername().get()).orElseThrow().getUuid()).isEqualTo(
|
||||
account.getUuid());
|
||||
|
||||
// reroll
|
||||
reservation = accountsManager.reserveUsername(account, "n00bkiller");
|
||||
account = reservation.account();
|
||||
account = accountsManager.confirmReservedUsername(
|
||||
account,
|
||||
reservation.reservedUsername(),
|
||||
reservation.reservationToken());
|
||||
|
||||
final String newUsername = account.getUsername().orElseThrow();
|
||||
assertThat(discriminator(account.getUsername().orElseThrow())).isNotEqualTo(discriminator);
|
||||
|
||||
// clear
|
||||
account = accountsManager.clearUsername(account);
|
||||
assertThat(accountsManager.getByUsername(newUsername)).isEmpty();
|
||||
assertThat(accountsManager.getByAccountIdentifier(account.getUuid()).orElseThrow().getUsername()).isEmpty();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReservationLapsed()
|
||||
throws InterruptedException, UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
// use a username generator that can retry a lot
|
||||
buildAccountsManager(1, 1, 1000000);
|
||||
|
||||
final Account account = accountsManager.create("+18005551111", "password", null, new AccountAttributes(),
|
||||
new ArrayList<>());
|
||||
AccountsManager.UsernameReservation reservation1 = accountsManager.reserveUsername(account, "n00bkiller");
|
||||
final String reservedUsername = reservation1.reservedUsername();
|
||||
|
||||
long past = Instant.now().minus(Duration.ofMinutes(1)).getEpochSecond();
|
||||
// force expiration
|
||||
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().updateItem(UpdateItemRequest.builder()
|
||||
.tableName(USERNAMES_TABLE_NAME)
|
||||
.key(Map.of(Accounts.ATTR_USERNAME, AttributeValues.fromString(reservedUsername)))
|
||||
.updateExpression("SET #ttl = :ttl")
|
||||
.expressionAttributeNames(Map.of("#ttl", Accounts.ATTR_TTL))
|
||||
.expressionAttributeValues(Map.of(":ttl", AttributeValues.fromLong(past)))
|
||||
.build());
|
||||
|
||||
int discriminator = discriminator(reservedUsername);
|
||||
|
||||
// use up all names except the reserved one
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
if (i == discriminator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ACCOUNTS_DYNAMO_EXTENSION.getDynamoDbClient().putItem(PutItemRequest.builder()
|
||||
.tableName(USERNAMES_TABLE_NAME)
|
||||
.item(Map.of(
|
||||
Accounts.KEY_ACCOUNT_UUID, AttributeValues.fromUUID(UUID.randomUUID()),
|
||||
Accounts.ATTR_USERNAME, AttributeValues.fromString(usernameGenerator.fromParts("n00bkiller", i))))
|
||||
.build());
|
||||
}
|
||||
|
||||
// a different account should be able to reserve it
|
||||
Account account2 = accountsManager.create("+18005552222", "password", null, new AccountAttributes(),
|
||||
new ArrayList<>());
|
||||
final AccountsManager.UsernameReservation reservation2 = accountsManager.reserveUsername(account2, "n00bkiller"
|
||||
);
|
||||
assertThat(reservation2.reservedUsername()).isEqualTo(reservedUsername);
|
||||
|
||||
assertThrows(UsernameNotAvailableException.class,
|
||||
() -> accountsManager.confirmReservedUsername(reservation1.account(), reservedUsername, reservation1.reservationToken()));
|
||||
accountsManager.confirmReservedUsername(reservation2.account(), reservedUsername, reservation2.reservationToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUsernameReserveClearSetReserved()
|
||||
throws InterruptedException, UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
Account account = accountsManager.create("+18005551111", "password", null, new AccountAttributes(),
|
||||
new ArrayList<>());
|
||||
account = accountsManager.setUsername(account, "n00bkiller", null);
|
||||
final AccountsManager.UsernameReservation reservation = accountsManager.reserveUsername(account, "other");
|
||||
account = reservation.account();
|
||||
|
||||
assertThat(reservation.reservedUsername()).startsWith("other");
|
||||
assertThat(account.getUsername()).hasValueSatisfying(s -> s.startsWith("n00bkiller"));
|
||||
|
||||
account = accountsManager.clearUsername(account);
|
||||
assertThat(account.getReservedUsernameHash()).isPresent();
|
||||
assertThat(account.getUsername()).isEmpty();
|
||||
|
||||
account = accountsManager.confirmReservedUsername(account, reservation.reservedUsername(), reservation.reservationToken());
|
||||
assertThat(account.getUsername()).hasValueSatisfying(s -> s.startsWith("other"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ import static org.mockito.Mockito.when;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.uuid.UUIDComparator;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -26,6 +29,7 @@ import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.function.Supplier;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
@@ -74,6 +78,7 @@ class AccountsTest {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private Clock mockClock;
|
||||
private DynamicConfigurationManager<DynamicConfiguration> mockDynamicConfigManager;
|
||||
private Accounts accounts;
|
||||
|
||||
@@ -130,7 +135,11 @@ class AccountsTest {
|
||||
when(mockDynamicConfigManager.getConfiguration())
|
||||
.thenReturn(new DynamicConfiguration());
|
||||
|
||||
mockClock = mock(Clock.class);
|
||||
when(mockClock.instant()).thenReturn(Instant.EPOCH);
|
||||
|
||||
this.accounts = new Accounts(
|
||||
mockClock,
|
||||
mockDynamicConfigManager,
|
||||
dynamoDbExtension.getDynamoDbClient(),
|
||||
dynamoDbExtension.getDynamoDbAsyncClient(),
|
||||
@@ -608,7 +617,7 @@ class AccountsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUsername() throws UsernameNotAvailableException {
|
||||
void testSetUsername() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
@@ -679,7 +688,7 @@ class AccountsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearUsername() throws UsernameNotAvailableException {
|
||||
void testClearUsername() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
@@ -704,7 +713,7 @@ class AccountsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearUsernameVersionMismatch() throws UsernameNotAvailableException {
|
||||
void testClearUsernameVersionMismatch() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
|
||||
@@ -719,6 +728,154 @@ class AccountsTest {
|
||||
assertThat(account.getUsername()).hasValueSatisfying(u -> assertThat(u).isEqualTo(username));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReservedUsername() {
|
||||
final Account account1 = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account1);
|
||||
final Account account2 = generateAccount("+18005552222", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account2);
|
||||
|
||||
final UUID token = accounts.reserveUsername(account1, "garfield", Duration.ofDays(1));
|
||||
assertThat(account1.getReservedUsernameHash()).get().isEqualTo(Accounts.reservedUsernameHash(account1.getUuid(), "garfield"));
|
||||
assertThat(account1.getUsername()).isEmpty();
|
||||
|
||||
// account 2 shouldn't be able to reserve the username
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.reserveUsername(account2, "garfield", Duration.ofDays(1)));
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.confirmUsername(account2, "garfield", UUID.randomUUID()));
|
||||
assertThat(accounts.getByUsername("garfield")).isEmpty();
|
||||
|
||||
accounts.confirmUsername(account1, "garfield", token);
|
||||
assertThat(account1.getReservedUsernameHash()).isEmpty();
|
||||
assertThat(account1.getUsername()).get().isEqualTo("garfield");
|
||||
assertThat(accounts.getByUsername("garfield").get().getUuid()).isEqualTo(account1.getUuid());
|
||||
|
||||
assertThat(dynamoDbExtension.getDynamoDbClient()
|
||||
.getItem(GetItemRequest.builder()
|
||||
.tableName(USERNAME_CONSTRAINT_TABLE_NAME)
|
||||
.key(Map.of(Accounts.ATTR_USERNAME, AttributeValues.fromString("garfield")))
|
||||
.build())
|
||||
.item())
|
||||
.doesNotContainKey(Accounts.ATTR_TTL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUsernameAvailable() {
|
||||
final Account account1 = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account1);
|
||||
|
||||
final String username = "unsinkablesam";
|
||||
|
||||
final UUID token = accounts.reserveUsername(account1, username, Duration.ofDays(1));
|
||||
assertThat(accounts.usernameAvailable(username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.empty(), username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.of(UUID.randomUUID()), username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.of(token), username)).isTrue();
|
||||
|
||||
accounts.confirmUsername(account1, username, token);
|
||||
assertThat(accounts.usernameAvailable(username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.empty(), username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.of(UUID.randomUUID()), username)).isFalse();
|
||||
assertThat(accounts.usernameAvailable(Optional.of(token), username)).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testReservedUsernameWrongToken() {
|
||||
final Account account = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
accounts.reserveUsername(account, "grumpy", Duration.ofDays(1));
|
||||
assertThat(account.getReservedUsernameHash())
|
||||
.get()
|
||||
.isEqualTo(Accounts.reservedUsernameHash(account.getUuid(), "grumpy"));
|
||||
assertThat(account.getUsername()).isEmpty();
|
||||
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.confirmUsername(account, "grumpy", UUID.randomUUID()));
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.setUsername(account, "grumpy"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReserveExpiredReservedUsername() {
|
||||
final Account account1 = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account1);
|
||||
final Account account2 = generateAccount("+18005552222", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account2);
|
||||
|
||||
accounts.reserveUsername(account1, "snowball#0002", Duration.ofDays(2));
|
||||
|
||||
Supplier<UUID> take = () -> accounts.reserveUsername(account2, "snowball#0002", Duration.ofDays(2));
|
||||
|
||||
for (int i = 0; i <= 2; i++) {
|
||||
when(mockClock.instant()).thenReturn(Instant.EPOCH.plus(Duration.ofDays(i)));
|
||||
assertThrows(ContestedOptimisticLockException.class, take::get);
|
||||
}
|
||||
|
||||
// after 2 days, can take the name
|
||||
when(mockClock.instant()).thenReturn(Instant.EPOCH.plus(Duration.ofDays(2)).plus(Duration.ofSeconds(1)));
|
||||
final UUID token = take.get();
|
||||
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.reserveUsername(account1, "snowball#0002", Duration.ofDays(2)));
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.setUsername(account1, "snowball#0002"));
|
||||
|
||||
accounts.confirmUsername(account2, "snowball#0002", token);
|
||||
assertThat(accounts.getByUsername("snowball#0002").get().getUuid()).isEqualTo(account2.getUuid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTakeExpiredReservedUsername() {
|
||||
final Account account1 = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account1);
|
||||
final Account account2 = generateAccount("+18005552222", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account2);
|
||||
|
||||
accounts.reserveUsername(account1, "snowball#0002", Duration.ofDays(2));
|
||||
|
||||
Runnable take = () -> accounts.setUsername(account2, "snowball#0002");
|
||||
|
||||
for (int i = 0; i <= 2; i++) {
|
||||
when(mockClock.instant()).thenReturn(Instant.EPOCH.plus(Duration.ofDays(i)));
|
||||
assertThrows(ContestedOptimisticLockException.class, take::run);
|
||||
}
|
||||
|
||||
// after 2 days, can take the name
|
||||
when(mockClock.instant()).thenReturn(Instant.EPOCH.plus(Duration.ofDays(2)).plus(Duration.ofSeconds(1)));
|
||||
take.run();
|
||||
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.reserveUsername(account1, "snowball#0002", Duration.ofDays(2)));
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.setUsername(account1, "snowball#0002"));
|
||||
assertThat(accounts.getByUsername("snowball#0002").get().getUuid()).isEqualTo(account2.getUuid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRetryReserveUsername() {
|
||||
final Account account = generateAccount("+18005551111", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
accounts.reserveUsername(account, "jorts", Duration.ofDays(2));
|
||||
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.reserveUsername(account, "jorts", Duration.ofDays(2)),
|
||||
"Shouldn't be able to re-reserve same username (would extend ttl)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReserveUsernameVersionConflict() {
|
||||
final Account account = generateAccount("+18005551234", UUID.randomUUID(), UUID.randomUUID());
|
||||
accounts.create(account);
|
||||
account.setVersion(account.getVersion() + 12);
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.reserveUsername(account, "salem", Duration.ofDays(1)));
|
||||
assertThrows(ContestedOptimisticLockException.class,
|
||||
() -> accounts.setUsername(account, "salem"));
|
||||
|
||||
}
|
||||
|
||||
private Device generateDevice(long id) {
|
||||
return DevicesHelper.createDevice(id);
|
||||
}
|
||||
|
||||
@@ -17,37 +17,37 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
class ReservedUsernamesTest {
|
||||
class ProhibitedUsernamesTest {
|
||||
|
||||
private static final String RESERVED_USERNAMES_TABLE_NAME = "reserved_usernames_test";
|
||||
|
||||
@RegisterExtension
|
||||
static DynamoDbExtension dynamoDbExtension = DynamoDbExtension.builder()
|
||||
.tableName(RESERVED_USERNAMES_TABLE_NAME)
|
||||
.hashKey(ReservedUsernames.KEY_PATTERN)
|
||||
.hashKey(ProhibitedUsernames.KEY_PATTERN)
|
||||
.attributeDefinition(AttributeDefinition.builder()
|
||||
.attributeName(ReservedUsernames.KEY_PATTERN)
|
||||
.attributeName(ProhibitedUsernames.KEY_PATTERN)
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private static final UUID RESERVED_FOR_UUID = UUID.randomUUID();
|
||||
|
||||
private ReservedUsernames reservedUsernames;
|
||||
private ProhibitedUsernames prohibitedUsernames;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
reservedUsernames =
|
||||
new ReservedUsernames(dynamoDbExtension.getDynamoDbClient(), RESERVED_USERNAMES_TABLE_NAME);
|
||||
prohibitedUsernames =
|
||||
new ProhibitedUsernames(dynamoDbExtension.getDynamoDbClient(), RESERVED_USERNAMES_TABLE_NAME);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void isReserved(final String username, final UUID uuid, final boolean expectReserved) {
|
||||
reservedUsernames.reserveUsername(".*myusername.*", RESERVED_FOR_UUID);
|
||||
reservedUsernames.reserveUsername("^foobar$", RESERVED_FOR_UUID);
|
||||
prohibitedUsernames.prohibitUsername(".*myusername.*", RESERVED_FOR_UUID);
|
||||
prohibitedUsernames.prohibitUsername("^foobar$", RESERVED_FOR_UUID);
|
||||
|
||||
assertEquals(expectReserved, reservedUsernames.isReserved(username, uuid));
|
||||
assertEquals(expectReserved, prohibitedUsernames.isProhibited(username, uuid));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> isReserved() {
|
||||
@@ -73,10 +73,13 @@ import org.whispersystems.textsecuregcm.entities.AccountIdentifierResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountIdentityResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.ApnRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.ChangePhoneNumberRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.ConfirmUsernameRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.GcmRegistrationId;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessage;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
|
||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.ReserveUsernameResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.UsernameRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.UsernameResponse;
|
||||
@@ -99,6 +102,7 @@ import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||
import org.whispersystems.textsecuregcm.storage.StoredVerificationCodeManager;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameNotAvailableException;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameReservationNotFoundException;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.Hex;
|
||||
@@ -121,6 +125,7 @@ class AccountControllerTest {
|
||||
|
||||
private static final UUID SENDER_REG_LOCK_UUID = UUID.randomUUID();
|
||||
private static final UUID SENDER_TRANSFER_UUID = UUID.randomUUID();
|
||||
private static final UUID RESERVATION_TOKEN = UUID.randomUUID();
|
||||
|
||||
private static final String ABUSIVE_HOST = "192.168.1.1";
|
||||
private static final String NICE_HOST = "127.0.0.1";
|
||||
@@ -144,6 +149,7 @@ class AccountControllerTest {
|
||||
private static RateLimiter smsVoicePrefixLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter autoBlockLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter usernameSetLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter usernameReserveLimiter = mock(RateLimiter.class);
|
||||
private static RateLimiter usernameLookupLimiter = mock(RateLimiter.class);
|
||||
private static SmsSender smsSender = mock(SmsSender.class);
|
||||
private static TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
|
||||
@@ -208,6 +214,7 @@ class AccountControllerTest {
|
||||
when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter);
|
||||
when(rateLimiters.getAutoBlockLimiter()).thenReturn(autoBlockLimiter);
|
||||
when(rateLimiters.getUsernameSetLimiter()).thenReturn(usernameSetLimiter);
|
||||
when(rateLimiters.getUsernameReserveLimiter()).thenReturn(usernameReserveLimiter);
|
||||
when(rateLimiters.getUsernameLookupLimiter()).thenReturn(usernameLookupLimiter);
|
||||
|
||||
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
|
||||
@@ -319,6 +326,7 @@ class AccountControllerTest {
|
||||
smsVoicePrefixLimiter,
|
||||
autoBlockLimiter,
|
||||
usernameSetLimiter,
|
||||
usernameReserveLimiter,
|
||||
usernameLookupLimiter,
|
||||
smsSender,
|
||||
turnTokenGenerator,
|
||||
@@ -1733,6 +1741,63 @@ class AccountControllerTest {
|
||||
assertThat(response.readEntity(UsernameResponse.class).username()).isEqualTo("n00bkiller#1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReserveUsername() throws UsernameNotAvailableException {
|
||||
when(accountsManager.reserveUsername(any(), eq("n00bkiller")))
|
||||
.thenReturn(new AccountsManager.UsernameReservation(null, "n00bkiller#1234", RESERVATION_TOKEN));
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/username/reserved")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new ReserveUsernameRequest("n00bkiller")));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(ReserveUsernameResponse.class))
|
||||
.satisfies(r -> r.username().equals("n00bkiller#1234"))
|
||||
.satisfies(r -> r.reservationToken().equals(RESERVATION_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommitUsername() throws UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
Account account = mock(Account.class);
|
||||
when(account.getUsername()).thenReturn(Optional.of("n00bkiller#1234"));
|
||||
when(accountsManager.confirmReservedUsername(any(), eq("n00bkiller#1234"), eq(RESERVATION_TOKEN))).thenReturn(account);
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/username/confirm")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new ConfirmUsernameRequest("n00bkiller#1234", RESERVATION_TOKEN)));
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(UsernameResponse.class).username()).isEqualTo("n00bkiller#1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommitUnreservedUsername() throws UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
when(accountsManager.confirmReservedUsername(any(), eq("n00bkiller#1234"), eq(RESERVATION_TOKEN)))
|
||||
.thenThrow(new UsernameReservationNotFoundException());
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/username/confirm")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new ConfirmUsernameRequest("n00bkiller#1234", RESERVATION_TOKEN)));
|
||||
assertThat(response.getStatus()).isEqualTo(409);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommitLapsedUsername() throws UsernameNotAvailableException, UsernameReservationNotFoundException {
|
||||
when(accountsManager.confirmReservedUsername(any(), eq("n00bkiller#1234"), eq(RESERVATION_TOKEN)))
|
||||
.thenThrow(new UsernameNotAvailableException());
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/username/confirm")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new ConfirmUsernameRequest("n00bkiller#1234", RESERVATION_TOKEN)));
|
||||
assertThat(response.getStatus()).isEqualTo(410);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetTakenUsername() {
|
||||
Response response =
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.whispersystems.textsecuregcm.storage.UsernameNotAvailableException;
|
||||
import org.whispersystems.textsecuregcm.util.UsernameGenerator;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
@@ -17,6 +18,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class UsernameGeneratorTest {
|
||||
|
||||
private static final Duration TTL = Duration.ofMinutes(5);
|
||||
|
||||
@ParameterizedTest(name = "[{index}]:{0} ({2})")
|
||||
@MethodSource
|
||||
public void nicknameValidation(String nickname, boolean valid, String testCaseName) {
|
||||
@@ -64,7 +67,7 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void zeroPadDiscriminators() {
|
||||
final UsernameGenerator generator = new UsernameGenerator(4, 5, 1);
|
||||
final UsernameGenerator generator = new UsernameGenerator(4, 5, 1, TTL);
|
||||
assertThat(generator.fromParts("test", 1)).isEqualTo("test#0001");
|
||||
assertThat(generator.fromParts("test", 123)).isEqualTo("test#0123");
|
||||
assertThat(generator.fromParts("test", 9999)).isEqualTo("test#9999");
|
||||
@@ -73,16 +76,16 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void expectedWidth() throws UsernameNotAvailableException {
|
||||
String username = new UsernameGenerator(1, 6, 1).generateAvailableUsername("test", t -> true);
|
||||
String username = new UsernameGenerator(1, 6, 1, TTL).generateAvailableUsername("test", t -> true);
|
||||
assertThat(extractDiscriminator(username)).isGreaterThan(0).isLessThan(10);
|
||||
|
||||
username = new UsernameGenerator(2, 6, 1).generateAvailableUsername("test", t -> true);
|
||||
username = new UsernameGenerator(2, 6, 1, TTL).generateAvailableUsername("test", t -> true);
|
||||
assertThat(extractDiscriminator(username)).isGreaterThan(0).isLessThan(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandDiscriminator() throws UsernameNotAvailableException {
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10);
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10, TTL);
|
||||
final String username = ug.generateAvailableUsername("test", allowDiscriminator(d -> d >= 10000));
|
||||
int discriminator = extractDiscriminator(username);
|
||||
assertThat(discriminator).isGreaterThanOrEqualTo(10000).isLessThan(100000);
|
||||
@@ -90,7 +93,7 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void expandDiscriminatorToMax() throws UsernameNotAvailableException {
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10);
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10, TTL);
|
||||
final String username = ug.generateAvailableUsername("test", allowDiscriminator(d -> d >= 100000));
|
||||
int discriminator = extractDiscriminator(username);
|
||||
assertThat(discriminator).isGreaterThanOrEqualTo(100000).isLessThan(1000000);
|
||||
@@ -98,7 +101,7 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void exhaustDiscriminator() {
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10);
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10, TTL);
|
||||
Assertions.assertThrows(UsernameNotAvailableException.class, () -> {
|
||||
// allow greater than our max width
|
||||
ug.generateAvailableUsername("test", allowDiscriminator(d -> d >= 1000000));
|
||||
@@ -107,7 +110,7 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void randomCoverageMinWidth() throws UsernameNotAvailableException {
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10);
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10, TTL);
|
||||
final Set<Integer> seen = new HashSet<>();
|
||||
for (int i = 0; i < 1000 && seen.size() < 9; i++) {
|
||||
seen.add(extractDiscriminator(ug.generateAvailableUsername("test", ignored -> true)));
|
||||
@@ -120,7 +123,7 @@ public class UsernameGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void randomCoverageMidWidth() throws UsernameNotAvailableException {
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10);
|
||||
UsernameGenerator ug = new UsernameGenerator(1, 6, 10, TTL);
|
||||
final Set<Integer> seen = new HashSet<>();
|
||||
for (int i = 0; i < 100000 && seen.size() < 90; i++) {
|
||||
seen.add(extractDiscriminator(ug.generateAvailableUsername("test", allowDiscriminator(d -> d >= 10))));
|
||||
|
||||
Reference in New Issue
Block a user