mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 16:18:00 +01:00
Use registration ID or creation timestamp in the transfer archive flow
This commit is contained in:
@@ -1082,31 +1082,39 @@ class DeviceControllerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordTransferArchiveUploaded() {
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void recordTransferArchiveUploaded(final Optional<Instant> deviceCreated, final Optional<Integer> registrationId) {
|
||||
final byte deviceId = Device.PRIMARY_ID + 1;
|
||||
final Instant deviceCreated = Instant.now().truncatedTo(ChronoUnit.MILLIS);
|
||||
final RemoteAttachment transferArchive =
|
||||
new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
when(rateLimiter.validateAsync(AuthHelper.VALID_UUID)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(accountsManager.recordTransferArchiveUpload(account, deviceId, deviceCreated, transferArchive))
|
||||
when(accountsManager.recordTransferArchiveUpload(account, deviceId, deviceCreated, registrationId, transferArchive))
|
||||
.thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/transfer_archive")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(deviceId, deviceCreated.toEpochMilli(), transferArchive),
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(deviceId, deviceCreated.map(Instant::toEpochMilli), registrationId, transferArchive),
|
||||
MediaType.APPLICATION_JSON_TYPE))) {
|
||||
|
||||
assertEquals(204, response.getStatus());
|
||||
|
||||
verify(accountsManager)
|
||||
.recordTransferArchiveUpload(account, deviceId, deviceCreated, transferArchive);
|
||||
.recordTransferArchiveUpload(account, deviceId, deviceCreated, registrationId, transferArchive);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Arguments> recordTransferArchiveUploaded() {
|
||||
return List.of(
|
||||
Arguments.of(Optional.empty(), Optional.of(123)),
|
||||
Arguments.of(Optional.of(Instant.now().truncatedTo(ChronoUnit.MILLIS)), Optional.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordTransferArchiveFailed() {
|
||||
final byte deviceId = Device.PRIMARY_ID + 1;
|
||||
@@ -1114,20 +1122,20 @@ class DeviceControllerTest {
|
||||
final RemoteAttachmentError transferFailure = new RemoteAttachmentError(RemoteAttachmentError.ErrorType.CONTINUE_WITHOUT_UPLOAD);
|
||||
|
||||
when(rateLimiter.validateAsync(AuthHelper.VALID_UUID)).thenReturn(CompletableFuture.completedFuture(null));
|
||||
when(accountsManager.recordTransferArchiveUpload(account, deviceId, deviceCreated, transferFailure))
|
||||
when(accountsManager.recordTransferArchiveUpload(account, deviceId, Optional.of(deviceCreated), Optional.empty(), transferFailure))
|
||||
.thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
try (final Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/transfer_archive")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(deviceId, deviceCreated.toEpochMilli(), transferFailure),
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(deviceId, Optional.of(deviceCreated.toEpochMilli()), Optional.empty(), transferFailure),
|
||||
MediaType.APPLICATION_JSON_TYPE))) {
|
||||
|
||||
assertEquals(204, response.getStatus());
|
||||
|
||||
verify(accountsManager)
|
||||
.recordTransferArchiveUpload(account, deviceId, deviceCreated, transferFailure);
|
||||
.recordTransferArchiveUpload(account, deviceId, Optional.of(deviceCreated), Optional.empty(), transferFailure);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1145,29 +1153,33 @@ class DeviceControllerTest {
|
||||
assertEquals(422, response.getStatus());
|
||||
|
||||
verify(accountsManager, never())
|
||||
.recordTransferArchiveUpload(any(), anyByte(), any(), any());
|
||||
.recordTransferArchiveUpload(any(), anyByte(), any(), any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
private static List<TransferArchiveUploadedRequest> recordTransferArchiveUploadedBadRequest() {
|
||||
private static List<Arguments> recordTransferArchiveUploadedBadRequest() {
|
||||
final RemoteAttachment validTransferArchive =
|
||||
new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("archive".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
return List.of(
|
||||
// Invalid device ID
|
||||
new TransferArchiveUploadedRequest((byte) -1, System.currentTimeMillis(), validTransferArchive),
|
||||
|
||||
// Invalid "created at" timestamp
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, -1, validTransferArchive),
|
||||
|
||||
// Missing CDN number
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, System.currentTimeMillis(),
|
||||
new RemoteAttachment(null, Base64.getUrlEncoder().encodeToString("archive".getBytes(StandardCharsets.UTF_8)))),
|
||||
|
||||
// Bad attachment key
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, System.currentTimeMillis(),
|
||||
new RemoteAttachment(3, "This is not a valid base64 string"))
|
||||
Arguments.argumentSet("Invalid device ID", new TransferArchiveUploadedRequest((byte) -1, Optional.of(System.currentTimeMillis()), Optional.empty(), validTransferArchive)),
|
||||
Arguments.argumentSet("Invalid \"created at\" timestamp",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.of((long) -1), Optional.empty(), validTransferArchive)),
|
||||
Arguments.argumentSet("Invalid registration ID - negative",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.empty(), Optional.of(-1), validTransferArchive)),
|
||||
Arguments.argumentSet("Invalid registration ID - too large",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.empty(), Optional.of(0x4000), validTransferArchive)),
|
||||
Arguments.argumentSet("Exactly one of \"created at\" timestamp and registration ID must be present - neither provided",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.empty(), Optional.empty(), validTransferArchive)),
|
||||
Arguments.argumentSet("Exactly one of \"created at\" timestamp and registration ID must be present - both provided",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.of(System.currentTimeMillis()), Optional.of(123), validTransferArchive)),
|
||||
Arguments.argumentSet("Missing CDN number",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.of(System.currentTimeMillis()), Optional.empty(),
|
||||
new RemoteAttachment(null, Base64.getUrlEncoder().encodeToString("archive".getBytes(StandardCharsets.UTF_8))))),
|
||||
Arguments.argumentSet("Bad attachment key",
|
||||
new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.of(System.currentTimeMillis()), Optional.empty(),
|
||||
new RemoteAttachment(3, "This is not a valid base64 string")))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1180,14 +1192,14 @@ class DeviceControllerTest {
|
||||
.target("/v1/devices/transfer_archive")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_UUID, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(Device.PRIMARY_ID, System.currentTimeMillis(),
|
||||
.put(Entity.entity(new TransferArchiveUploadedRequest(Device.PRIMARY_ID, Optional.of(System.currentTimeMillis()), Optional.empty(),
|
||||
new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)))),
|
||||
MediaType.APPLICATION_JSON_TYPE))) {
|
||||
|
||||
assertEquals(429, response.getStatus());
|
||||
|
||||
verify(accountsManager, never())
|
||||
.recordTransferArchiveUpload(any(), anyByte(), any(), any());
|
||||
.recordTransferArchiveUpload(any(), anyByte(), any(), any(), any());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.whispersystems.textsecuregcm.auth.DisconnectionRequestManager;
|
||||
import org.whispersystems.textsecuregcm.entities.RemoteAttachmentError;
|
||||
import org.whispersystems.textsecuregcm.entities.RestoreAccountRequest;
|
||||
@@ -28,6 +31,7 @@ import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -87,18 +91,22 @@ public class AccountsManagerDeviceTransferIntegrationTest {
|
||||
accountsManager.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForTransferArchive() {
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void waitForTransferArchive(
|
||||
final Optional<Long> recordUploadDeviceCreated,
|
||||
final Optional<Integer> recordUploadRegistrationId) {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
|
||||
final RemoteAttachment transferArchive =
|
||||
new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("transfer-archive".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(device.getId()).thenReturn(deviceId);
|
||||
when(device.getCreated()).thenReturn(deviceCreated);
|
||||
when(device.getCreated()).thenReturn(recordUploadDeviceCreated.orElse(System.currentTimeMillis()));
|
||||
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(recordUploadRegistrationId.orElse(1));
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
|
||||
@@ -111,66 +119,106 @@ public class AccountsManagerDeviceTransferIntegrationTest {
|
||||
|
||||
assertEquals(Optional.empty(), displacedFuture.join());
|
||||
|
||||
accountsManager.recordTransferArchiveUpload(account, deviceId, Instant.ofEpochMilli(deviceCreated), transferArchive).join();
|
||||
accountsManager.recordTransferArchiveUpload(account, deviceId, recordUploadDeviceCreated.map(Instant::ofEpochMilli), recordUploadRegistrationId, transferArchive).join();
|
||||
|
||||
assertEquals(Optional.of(transferArchive), activeFuture.join());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForTransferArchiveAlreadyAdded() {
|
||||
private static List<Arguments> waitForTransferArchive() {
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
final int registrationId = 123;
|
||||
|
||||
return List.of(
|
||||
Arguments.of(Optional.empty(), Optional.of(registrationId)),
|
||||
Arguments.of(Optional.of(deviceCreated), Optional.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void waitForTransferArchiveAlreadyAdded(
|
||||
final Optional<Long> recordUploadDeviceCreated,
|
||||
final Optional<Integer> recordUploadRegistrationId) {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
|
||||
|
||||
final RemoteAttachment transferArchive =
|
||||
new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("transfer-archive".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(device.getId()).thenReturn(deviceId);
|
||||
when(device.getCreated()).thenReturn(deviceCreated);
|
||||
when(device.getCreated()).thenReturn(recordUploadDeviceCreated.orElse(System.currentTimeMillis()));
|
||||
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(recordUploadRegistrationId.orElse(1));
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
|
||||
|
||||
accountsManager.recordTransferArchiveUpload(account, deviceId, Instant.ofEpochMilli(deviceCreated), transferArchive).join();
|
||||
accountsManager.recordTransferArchiveUpload(account, deviceId, recordUploadDeviceCreated.map(Instant::ofEpochMilli), recordUploadRegistrationId, transferArchive).join();
|
||||
|
||||
assertEquals(Optional.of(transferArchive),
|
||||
accountsManager.waitForTransferArchive(account, device, Duration.ofSeconds(5)).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForErrorTransferArchive() {
|
||||
private static List<Arguments> waitForTransferArchiveAlreadyAdded() {
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
final int registrationId = 123;
|
||||
|
||||
return List.of(
|
||||
Arguments.of(Optional.empty(), Optional.of(registrationId)),
|
||||
Arguments.of(Optional.of(deviceCreated), Optional.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void waitForErrorTransferArchive(
|
||||
final Optional<Long> recordUploadDeviceCreated,
|
||||
final Optional<Integer> recordUploadRegistrationId) {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
|
||||
final RemoteAttachmentError transferArchiveError =
|
||||
new RemoteAttachmentError(RemoteAttachmentError.ErrorType.CONTINUE_WITHOUT_UPLOAD);
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(device.getId()).thenReturn(deviceId);
|
||||
when(device.getCreated()).thenReturn(deviceCreated);
|
||||
when(device.getCreated()).thenReturn(recordUploadDeviceCreated.orElse(System.currentTimeMillis()));
|
||||
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(recordUploadRegistrationId.orElse(1));
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
|
||||
|
||||
accountsManager
|
||||
.recordTransferArchiveUpload(account, deviceId, Instant.ofEpochMilli(deviceCreated), transferArchiveError)
|
||||
.join();
|
||||
accountsManager.recordTransferArchiveUpload(account, deviceId, recordUploadDeviceCreated.map(Instant::ofEpochMilli),
|
||||
recordUploadRegistrationId, transferArchiveError).join();
|
||||
|
||||
assertEquals(Optional.of(transferArchiveError),
|
||||
accountsManager.waitForTransferArchive(account, device, Duration.ofSeconds(5)).join());
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForTransferArchiveTimeout() {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
private static List<Arguments> waitForErrorTransferArchive() {
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
final int registrationId = 123;
|
||||
|
||||
return List.of(
|
||||
Arguments.of(Optional.empty(), Optional.of(registrationId)),
|
||||
Arguments.of(Optional.of(deviceCreated), Optional.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||
void waitForTransferArchiveTimeout(
|
||||
final Optional<Long> recordUploadDeviceCreated,
|
||||
final Optional<Integer> recordUploadRegistrationId) {
|
||||
final UUID accountIdentifier = UUID.randomUUID();
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(device.getId()).thenReturn(deviceId);
|
||||
when(device.getCreated()).thenReturn(deviceCreated);
|
||||
when(device.getCreated()).thenReturn(recordUploadDeviceCreated.orElse(System.currentTimeMillis()));
|
||||
when(device.getRegistrationId(IdentityType.ACI)).thenReturn(recordUploadRegistrationId.orElse(1));
|
||||
|
||||
final Account account = mock(Account.class);
|
||||
when(account.getIdentifier(IdentityType.ACI)).thenReturn(accountIdentifier);
|
||||
@@ -179,6 +227,16 @@ public class AccountsManagerDeviceTransferIntegrationTest {
|
||||
accountsManager.waitForTransferArchive(account, device, Duration.ofMillis(1)).join());
|
||||
}
|
||||
|
||||
private static List<Arguments> waitForTransferArchiveTimeout() {
|
||||
final long deviceCreated = System.currentTimeMillis();
|
||||
final int registrationId = 123;
|
||||
|
||||
return List.of(
|
||||
Arguments.of(Optional.empty(), Optional.of(registrationId)),
|
||||
Arguments.of(Optional.of(deviceCreated), Optional.empty())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void waitForRestoreAccountRequest() {
|
||||
final String token = RandomStringUtils.secure().nextAlphanumeric(16);
|
||||
|
||||
@@ -57,6 +57,7 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
@@ -82,6 +83,8 @@ import org.whispersystems.textsecuregcm.controllers.MismatchedDevicesException;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.ECSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.KEMSignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.RemoteAttachment;
|
||||
import org.whispersystems.textsecuregcm.entities.TransferArchiveResult;
|
||||
import org.whispersystems.textsecuregcm.identity.AciServiceIdentifier;
|
||||
import org.whispersystems.textsecuregcm.identity.IdentityType;
|
||||
import org.whispersystems.textsecuregcm.identity.PniServiceIdentifier;
|
||||
@@ -1498,6 +1501,61 @@ class AccountsManagerTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFirstSuccessfulTransferArchiveCompletableFutureOneTimeout() {
|
||||
// First future times out, second one completes successfully
|
||||
final RemoteAttachment transferArchive = new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> timeoutFuture = new CompletableFuture<>();
|
||||
timeoutFuture.completeOnTimeout(Optional.empty(), 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> successfulFuture = new CompletableFuture<>();
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> result =
|
||||
AccountsManager.firstSuccessfulTransferArchiveFuture(List.of(timeoutFuture, successfulFuture));
|
||||
|
||||
CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS)
|
||||
.execute(() -> successfulFuture.complete(Optional.of(transferArchive)));
|
||||
|
||||
final Optional<TransferArchiveResult> maybeTransferArchive = result.join();
|
||||
assertTrue(maybeTransferArchive.isPresent());
|
||||
assertEquals(transferArchive, maybeTransferArchive.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFirstSuccessfulTransferArchiveCompletableFutureBothTimeout() {
|
||||
// Both futures time out
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> firstTimeoutFuture = new CompletableFuture<>();
|
||||
firstTimeoutFuture.completeOnTimeout(Optional.empty(), 10, TimeUnit.MILLISECONDS);
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> secondTimeoutFuture = new CompletableFuture<>();
|
||||
secondTimeoutFuture.completeOnTimeout(Optional.empty(), 10, TimeUnit.MILLISECONDS);
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> result =
|
||||
AccountsManager.firstSuccessfulTransferArchiveFuture(List.of(firstTimeoutFuture, secondTimeoutFuture));
|
||||
|
||||
assertTrue(result.join().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFirstSuccessfulTransferArchiveCompletableFuture() {
|
||||
// First future completes successfully, second one times out
|
||||
final RemoteAttachment transferArchive = new RemoteAttachment(3, Base64.getUrlEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> successfulFuture = new CompletableFuture<>();
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> timeoutFuture = new CompletableFuture<>();
|
||||
timeoutFuture.completeOnTimeout(Optional.empty(), 50, TimeUnit.MILLISECONDS);
|
||||
|
||||
final CompletableFuture<Optional<TransferArchiveResult>> result =
|
||||
AccountsManager.firstSuccessfulTransferArchiveFuture(List.of(successfulFuture, timeoutFuture));
|
||||
successfulFuture.complete(Optional.of(transferArchive));
|
||||
|
||||
final Optional<TransferArchiveResult> maybeTransferArchive = result.join();
|
||||
assertTrue(maybeTransferArchive.isPresent());
|
||||
assertEquals(transferArchive, maybeTransferArchive.get());
|
||||
}
|
||||
|
||||
private static List<Arguments> validateCompleteDeviceList() {
|
||||
final byte deviceId = Device.PRIMARY_ID;
|
||||
final byte extraDeviceId = deviceId + 1;
|
||||
|
||||
Reference in New Issue
Block a user