| /* |
| * Copyright 2022 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.android.libraries.mobiledatadownload; |
| |
| import static android.system.Os.readlink; |
| import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_CHECKSUM; |
| import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_GROUP_NAME; |
| import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_ID; |
| import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_SIZE; |
| import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_URL; |
| import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; |
| import static com.google.android.libraries.mobiledatadownload.tracing.TracePropagation.propagateCallable; |
| import static com.google.common.truth.Truth.assertThat; |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| import static org.junit.Assert.assertThrows; |
| import static org.junit.Assert.fail; |
| import static org.mockito.Mockito.eq; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| |
| import android.accounts.Account; |
| import android.content.Context; |
| import android.net.Uri; |
| import android.util.Log; |
| import androidx.test.core.app.ApplicationProvider; |
| import com.google.android.libraries.mobiledatadownload.account.AccountUtil; |
| import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; |
| import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; |
| import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; |
| import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri; |
| import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUriAdapter; |
| import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend; |
| import com.google.android.libraries.mobiledatadownload.file.openers.ReadStringOpener; |
| import com.google.android.libraries.mobiledatadownload.file.openers.WriteStringOpener; |
| import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil; |
| import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; |
| import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader; |
| import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; |
| import com.google.android.libraries.mobiledatadownload.testing.TestFileDownloader; |
| import com.google.android.libraries.mobiledatadownload.testing.TestFlags; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Supplier; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| import com.google.common.util.concurrent.ListeningScheduledExecutorService; |
| import com.google.common.util.concurrent.MoreExecutors; |
| import com.google.mobiledatadownload.ClientConfigProto.ClientFile; |
| import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup; |
| import com.google.mobiledatadownload.DownloadConfigProto.DataFile; |
| import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; |
| import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy; |
| import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; |
| import com.google.mobiledatadownload.LogProto.MddDownloadResultLog; |
| import com.google.mobiledatadownload.LogProto.MddLogData; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.TimeoutException; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameter; |
| import org.junit.runners.Parameterized.Parameters; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| |
| // NOTE: TestParameterInjector is preferred for parameterized tests, but it has a API |
| // level constraint of >= 24 while MDD has a constraint of >= 16. To prevent basic regressions, run |
| // this test using junit's Parameterized TestRunner, which supports all API levels. |
| @RunWith(Parameterized.class) |
| public class MobileDataDownloadIntegrationTest { |
| |
| private static final String TAG = "MobileDataDownloadIntegrationTest"; |
| private static final int MAX_HANDLE_TASK_WAIT_TIME_SECS = 300; |
| private static final int MAX_MDD_API_WAIT_TIME_SECS = 5; |
| |
| private static final String TEST_DATA_RELATIVE_PATH = |
| "third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/"; |
| |
| private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = |
| MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(2)); |
| |
| private static final Context context = ApplicationProvider.getApplicationContext(); |
| private final NetworkUsageMonitor networkUsageMonitor = |
| new NetworkUsageMonitor(context, new FakeTimeSource()); |
| |
| private final SynchronousFileStorage fileStorage = |
| new SynchronousFileStorage( |
| ImmutableList.of(AndroidFileBackend.builder(context).build(), new JavaFileBackend()), |
| ImmutableList.of(), |
| ImmutableList.of(networkUsageMonitor)); |
| |
| private final TestFlags flags = new TestFlags(); |
| |
| private ListeningExecutorService controlExecutor; |
| |
| private MobileDataDownload mobileDataDownload; |
| |
| @Mock private Logger mockLogger; |
| @Mock private TaskScheduler mockTaskScheduler; |
| |
| @Rule public final MockitoRule mocks = MockitoJUnit.rule(); |
| |
| @Parameter public ExecutorType controlExecutorType; |
| |
| @Parameters |
| public static Collection<Object[]> data() { |
| return Arrays.asList( |
| new Object[][] { |
| {ExecutorType.SINGLE_THREADED}, {ExecutorType.MULTI_THREADED}, |
| }); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| |
| flags.enableZipFolder = Optional.of(true); |
| |
| controlExecutor = controlExecutorType.executor(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| } |
| |
| @Test |
| public void download_success_fileGroupDownloaded() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| String debugString = mobileDataDownload.getDebugInfoAsString(); |
| Log.i(TAG, "MDD Lib dump:"); |
| for (String line : debugString.split("\n", -1)) { |
| Log.i(TAG, line); |
| } |
| |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); |
| } |
| |
| @Test |
| public void download_withCustomValidator() throws Exception { |
| CustomFileGroupValidator validator = |
| fileGroup -> { |
| if (!fileGroup.getGroupName().equals(FILE_GROUP_NAME)) { |
| return Futures.immediateFuture(true); |
| } |
| return MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR) |
| .submit( |
| propagateCallable( |
| () -> { |
| SynchronousFileStorage storage = |
| new SynchronousFileStorage( |
| ImmutableList.of(AndroidFileBackend.builder(context).build())); |
| for (ClientFile file : fileGroup.getFileList()) { |
| if (!storage.exists(Uri.parse(file.getFileUri()))) { |
| return false; |
| } |
| } |
| return true; |
| })); |
| }; |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .setCustomFileGroupValidatorOptional(Optional.of(validator)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| ClientFileGroup clientFileGroup = |
| mobileDataDownload |
| .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); |
| } |
| |
| @Test |
| public void download_success_maintenanceLogsNetworkUsage() throws Exception { |
| flags.networkStatsLoggingSampleInterval = Optional.of(1); |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| // This should flush the logs from NetworkLogger. |
| mobileDataDownload |
| .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); |
| |
| ArgumentCaptor<MddLogData> logDataCaptor = ArgumentCaptor.forClass(MddLogData.class); |
| // 1056 is the tag number for MddClientEvent.Code.EVENT_CODE_UNSPECIFIED. |
| verify(mockLogger, times(1)).log(logDataCaptor.capture(), /* eventCode= */ eq(1056)); |
| |
| List<MddLogData> logDataList = logDataCaptor.getAllValues(); |
| assertThat(logDataList).hasSize(1); |
| MddLogData logData = logDataList.get(0); |
| |
| Void mddNetworkStats = null; |
| |
| // Network status changes depending on emulator: |
| boolean isCellular = NetworkUsageMonitor.isCellular(context); |
| } |
| |
| @Test |
| public void corrupted_files_detectedDuringMaintenance() throws Exception { |
| flags.mddDefaultSampleInterval = Optional.of(1); |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| fileStorage.open( |
| Uri.parse(clientFileGroup.getFile(0).getFileUri()), WriteStringOpener.create("c0rrupt3d")); |
| |
| // Bad file is detected during maintenance. |
| mobileDataDownload |
| .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // File group is re-downloaded. |
| mobileDataDownload |
| .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // Re-load the file group since the on-disk URIs will have changed. |
| clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| assertThat( |
| fileStorage.open( |
| Uri.parse(clientFileGroup.getFile(0).getFileUri()), ReadStringOpener.create())) |
| .isNotEqualTo("c0rrupt3d"); |
| } |
| |
| @Test |
| public void delete_files_detectedDuringMaintenance() throws Exception { |
| flags.mddDefaultSampleInterval = Optional.of(1); |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| fileStorage.deleteFile(Uri.parse(clientFileGroup.getFile(0).getFileUri())); |
| |
| // Bad file is detected during maintenance. |
| mobileDataDownload |
| .handleTask(TaskScheduler.MAINTENANCE_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // File group is re-downloaded. |
| mobileDataDownload |
| .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // Re-load the file group since the on-disk URIs will have changed. |
| clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| assertThat(fileStorage.exists(Uri.parse(clientFileGroup.getFile(0).getFileUri()))).isTrue(); |
| } |
| |
| @Test |
| public void remove_withAccount_fileGroupRemains() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| // Remove the file group with account doesn't change anything, because the test group is not |
| // associated with any account. |
| Account account = AccountUtil.create("name", "google"); |
| assertThat(account).isNotNull(); |
| assertThat( |
| mobileDataDownload |
| .removeFileGroup( |
| RemoveFileGroupRequest.newBuilder() |
| .setGroupName(FILE_GROUP_NAME) |
| .setAccountOptional(Optional.of(account)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .isTrue(); |
| |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| verifyClientFile(clientFileGroup.getFileList().get(0), FILE_ID, FILE_SIZE); |
| } |
| |
| @Test |
| public void remove_withoutAccount_fileGroupRemoved() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| // Remove the file group will make the file group not accessible from clients. |
| assertThat( |
| mobileDataDownload |
| .removeFileGroup( |
| RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .isTrue(); |
| |
| ClientFileGroup clientFileGroup = |
| mobileDataDownload |
| .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| assertThat(clientFileGroup).isNull(); |
| } |
| |
| @Test |
| public void removeFileGroupsByFilter_removesMatchingGroups() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .build(); |
| |
| // Remove All Groups to clear state |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Tear down: remove remaining group to prevent cross test errors |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| } |
| |
| @Test |
| public void removeFileGroupsByFilter_whenAccountSpecified_removesMatchingAccountDependentGroups() |
| throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .build(); |
| |
| // Remove all groups |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Setup account |
| Account account = AccountUtil.create("name", "google"); |
| |
| // Setup two groups, 1 with account and 1 without an account |
| DataFileGroup fileGroupWithoutAccount = |
| TestFileGroupPopulator.createDataFileGroup( |
| FILE_GROUP_NAME, |
| context.getPackageName(), |
| new String[] {FILE_ID}, |
| new int[] {FILE_SIZE}, |
| new String[] {FILE_CHECKSUM}, |
| new String[] {FILE_URL}, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK) |
| .toBuilder() |
| .build(); |
| DataFileGroup fileGroupWithAccount = |
| fileGroupWithoutAccount.toBuilder().setGroupName(FILE_GROUP_NAME + "_2").build(); |
| |
| // Add both groups to MDD |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroupWithoutAccount).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder() |
| .setDataFileGroup(fileGroupWithAccount) |
| .setAccountOptional(Optional.of(account)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Verify that both groups are present |
| assertThat( |
| mobileDataDownload |
| .getFileGroupsByFilter( |
| GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .hasSize(2); |
| |
| // Remove file groups with given account and source |
| mobileDataDownload |
| .removeFileGroupsByFilter( |
| RemoveFileGroupsByFilterRequest.newBuilder() |
| .setAccountOptional(Optional.of(account)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Check that only account-independent group remains |
| ImmutableList<ClientFileGroup> remainingGroups = |
| mobileDataDownload |
| .getFileGroupsByFilter( |
| GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| assertThat(remainingGroups).hasSize(1); |
| assertThat(remainingGroups.get(0).getGroupName()).isEqualTo(FILE_GROUP_NAME); |
| |
| // Tear down: remove remaining group to prevent cross test errors |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| } |
| |
| @Test |
| public void |
| removeFileGroupsByFilter_whenAccountNotSpecified_removesMatchingAccountIndependentGroups() |
| throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| // Remove all groups |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Setup account |
| Account account = AccountUtil.create("name", "google"); |
| |
| // Setup two groups, 1 with account and 1 without an account |
| DataFileGroup fileGroupWithoutAccount = |
| TestFileGroupPopulator.createDataFileGroup( |
| FILE_GROUP_NAME, |
| context.getPackageName(), |
| new String[] {FILE_ID}, |
| new int[] {FILE_SIZE}, |
| new String[] {FILE_CHECKSUM}, |
| new String[] {FILE_URL}, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK) |
| .toBuilder() |
| .build(); |
| DataFileGroup fileGroupWithAccount = |
| fileGroupWithoutAccount.toBuilder().setGroupName(FILE_GROUP_NAME + "_2").build(); |
| |
| // Add both groups to MDD |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroupWithoutAccount).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder() |
| .setDataFileGroup(fileGroupWithAccount) |
| .setAccountOptional(Optional.of(account)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Verify that both groups are present |
| assertThat( |
| mobileDataDownload |
| .getFileGroupsByFilter( |
| GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .hasSize(2); |
| |
| // Remove file groups with given source only |
| mobileDataDownload |
| .removeFileGroupsByFilter(RemoveFileGroupsByFilterRequest.newBuilder().build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Check that only account-dependent group remains |
| ImmutableList<ClientFileGroup> remainingGroups = |
| mobileDataDownload |
| .getFileGroupsByFilter( |
| GetFileGroupsByFilterRequest.newBuilder().setIncludeAllGroups(true).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| assertThat(remainingGroups).hasSize(1); |
| assertThat(remainingGroups.get(0).getGroupName()).isEqualTo(FILE_GROUP_NAME + "_2"); |
| |
| // Tear down: remove remaining group to prevent cross test errors |
| mobileDataDownload |
| .removeFileGroupsByFilter( |
| RemoveFileGroupsByFilterRequest.newBuilder() |
| .setAccountOptional(Optional.of(account)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| } |
| |
| @Test |
| public void download_failure_throwsDownloadException() throws Exception { |
| flags.mddDefaultSampleInterval = Optional.of(1); |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| DataFileGroup dataFileGroup = |
| TestFileGroupPopulator.createDataFileGroup( |
| FILE_GROUP_NAME, |
| context.getPackageName(), |
| new String[] {"one", "two"}, |
| new int[] {1000, 2000}, |
| new String[] {"checksum1", "checksum2"}, |
| new String[] { |
| "http://www.gstatic.com/", // This url is not secure. |
| "https://www.gstatic.com/does_not_exist" // This url does not exist. |
| }, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); |
| |
| assertThat( |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .isTrue(); |
| |
| ListenableFuture<ClientFileGroup> downloadFuture = |
| mobileDataDownload.downloadFileGroup( |
| DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); |
| |
| ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); |
| assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); |
| AggregateException cause = (AggregateException) exception.getCause(); |
| assertThat(cause).isNotNull(); |
| ImmutableList<Throwable> failures = cause.getFailures(); |
| assertThat(failures).hasSize(2); |
| assertThat(failures.get(0)).isInstanceOf(DownloadException.class); |
| assertThat(failures.get(1)).isInstanceOf(DownloadException.class); |
| } |
| |
| @Test |
| public void download_failure_logsEvent() throws Exception { |
| flags.mddDefaultSampleInterval = Optional.of(1); |
| |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TestFileGroupPopulator(context)) |
| .build(); |
| |
| DataFileGroup dataFileGroup = |
| TestFileGroupPopulator.createDataFileGroup( |
| FILE_GROUP_NAME, |
| context.getPackageName(), |
| new String[] {"one", "two"}, |
| new int[] {1000, 2000}, |
| new String[] {"checksum1", "checksum2"}, |
| new String[] { |
| "http://www.gstatic.com/", // This url is not secure. |
| "https://www.gstatic.com/does_not_exist" // This url does not exist. |
| }, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); |
| |
| assertThat( |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder().setDataFileGroup(dataFileGroup).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) |
| .isTrue(); |
| |
| ListenableFuture<ClientFileGroup> downloadFuture = |
| mobileDataDownload.downloadFileGroup( |
| DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); |
| |
| assertThrows(ExecutionException.class, downloadFuture::get); |
| |
| if (controlExecutorType.equals(ExecutorType.SINGLE_THREADED)) { |
| // Single-threaded executor step requires some time to allow logging to finish. |
| // TODO: Investigate whether TestingTaskBarrier can be used here to wait for |
| // executor become idle. |
| Thread.sleep(500); |
| } |
| |
| ArgumentCaptor<MddLogData> logDataCaptor = ArgumentCaptor.forClass(MddLogData.class); |
| // 1068 is the tag number for MddClientEvent.Code.DATA_DOWNLOAD_RESULT_LOG. |
| verify(mockLogger, times(2)).log(logDataCaptor.capture(), /* eventCode= */ eq(1068)); |
| |
| List<MddLogData> logData = logDataCaptor.getAllValues(); |
| assertThat(logData).hasSize(2); |
| |
| MddDownloadResultLog downloadResultLog1 = logData.get(0).getMddDownloadResultLog(); |
| MddDownloadResultLog downloadResultLog2 = logData.get(1).getMddDownloadResultLog(); |
| assertThat(downloadResultLog1.getResult()).isEqualTo(MddDownloadResult.Code.INSECURE_URL_ERROR); |
| assertThat(downloadResultLog1.getDataDownloadFileGroupStats().getFileGroupName()) |
| .isEqualTo(FILE_GROUP_NAME); |
| assertThat(downloadResultLog1.getDataDownloadFileGroupStats().getOwnerPackage()) |
| .isEqualTo(context.getPackageName()); |
| assertThat(downloadResultLog2.getResult()) |
| .isEqualTo(MddDownloadResult.Code.ANDROID_DOWNLOADER_HTTP_ERROR); |
| assertThat(downloadResultLog2.getDataDownloadFileGroupStats().getFileGroupName()) |
| .isEqualTo(FILE_GROUP_NAME); |
| assertThat(downloadResultLog2.getDataDownloadFileGroupStats().getOwnerPackage()) |
| .isEqualTo(context.getPackageName()); |
| } |
| |
| @Test |
| public void download_zipFile_unzippedAfterDownload() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new ZipFolderFileGroupPopulator(context)) |
| .build(); |
| |
| waitForHandleTask(); |
| |
| ClientFileGroup clientFileGroup = |
| getAndVerifyClientFileGroup(ZipFolderFileGroupPopulator.FILE_GROUP_NAME, 3); |
| |
| for (ClientFile clientFile : clientFileGroup.getFileList()) { |
| if ("/zip1.txt".equals(clientFile.getFileId())) { |
| verifyClientFile(clientFile, "/zip1.txt", 11); |
| } else if ("/zip2.txt".equals(clientFile.getFileId())) { |
| verifyClientFile(clientFile, "/zip2.txt", 11); |
| } else if ("/sub_folder/zip3.txt".equals(clientFile.getFileId())) { |
| verifyClientFile(clientFile, "/sub_folder/zip3.txt", 25); |
| } else { |
| fail("Unexpect file:" + clientFile.getFileId()); |
| } |
| } |
| } |
| |
| @Test |
| public void download_cancelDuringDownload_downloadCancelled() throws Exception { |
| BlockingFileDownloader blockingFileDownloader = new BlockingFileDownloader(DOWNLOAD_EXECUTOR); |
| |
| Supplier<FileDownloader> fakeFileDownloaderSupplier = () -> blockingFileDownloader; |
| |
| mobileDataDownload = |
| builderForTest().setFileDownloaderSupplier(fakeFileDownloaderSupplier).build(); |
| |
| // Register the file group and trigger download. |
| mobileDataDownload |
| .addFileGroup( |
| AddFileGroupRequest.newBuilder() |
| .setDataFileGroup( |
| TestFileGroupPopulator.createDataFileGroup( |
| FILE_GROUP_NAME, |
| context.getPackageName(), |
| new String[] {FILE_ID}, |
| new int[] {FILE_SIZE}, |
| new String[] {FILE_CHECKSUM}, |
| new String[] {FILE_URL}, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)) |
| .build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| ListenableFuture<ClientFileGroup> downloadFuture = |
| mobileDataDownload.downloadFileGroup( |
| DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); |
| |
| // Wait for download to be scheduled. The future shouldn't be done yet. |
| blockingFileDownloader.waitForDownloadStarted(); |
| assertThat(downloadFuture.isDone()).isFalse(); |
| |
| // Now remove the file group from MDD, which would cancel any ongoing download. |
| mobileDataDownload |
| .removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| // Now let the download future finish. |
| blockingFileDownloader.finishDownloading(); |
| |
| // Make sure that the download has been canceled and leads to cancelled future. |
| ExecutionException exception = |
| assertThrows( |
| ExecutionException.class, |
| () -> downloadFuture.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS)); |
| assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); |
| AggregateException cause = (AggregateException) exception.getCause(); |
| assertThat(cause).isNotNull(); |
| ImmutableList<Throwable> failures = cause.getFailures(); |
| assertThat(failures).hasSize(1); |
| assertThat(failures.get(0)).isInstanceOf(CancellationException.class); |
| } |
| |
| @Test |
| public void download_twoStepDownload_targetFileDownloaded() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .addFileGroupPopulator(new TwoStepPopulator(context, fileStorage)) |
| .build(); |
| |
| // Add step1 file group to MDD. |
| DataFileGroup step1FileGroup = |
| TestFileGroupPopulator.createDataFileGroup( |
| "step1-file-group", |
| context.getPackageName(), |
| new String[] {"step1_id"}, |
| new int[] {57}, |
| new String[] {""}, |
| new String[] {"https://www.gstatic.com/icing/idd/sample_group/step1.txt"}, |
| DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); |
| |
| ListenableFuture<Boolean> unused = |
| mobileDataDownload.addFileGroup( |
| AddFileGroupRequest.newBuilder().setDataFileGroup(step1FileGroup).build()); |
| |
| // This will trigger refreshing of FileGroupPopulators and downloading. |
| mobileDataDownload |
| .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // Now verify that the step1-file-group is downloaded and then TwoStepPopulator will add |
| // step2-file-group and it was downloaded too in one cycle (one call of handleTask). |
| |
| // Verify step1-file-group. |
| ClientFileGroup clientFileGroup = getAndVerifyClientFileGroup("step1-file-group", 1); |
| verifyClientFile(clientFileGroup.getFile(0), "step1_id", 57); |
| |
| // Verify step2-file-group. |
| clientFileGroup = getAndVerifyClientFileGroup("step2-file-group", 1); |
| verifyClientFile(clientFileGroup.getFile(0), "step2_id", 13); |
| } |
| |
| @Test |
| public void download_relativeFilePaths_createsSymlinks() throws Exception { |
| AndroidUriAdapter adapter = AndroidUriAdapter.forContext(context); |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .build(); |
| |
| DataFileGroup fileGroup = |
| DataFileGroup.newBuilder() |
| .setGroupName(FILE_GROUP_NAME) |
| .setOwnerPackage(context.getPackageName()) |
| .setPreserveFilenamesAndIsolateFiles(true) |
| .addFile( |
| DataFile.newBuilder() |
| .setFileId(FILE_ID) |
| .setByteSize(FILE_SIZE) |
| .setChecksumType(DataFile.ChecksumType.DEFAULT) |
| .setChecksum(FILE_CHECKSUM) |
| .setUrlToDownload(FILE_URL) |
| .setRelativeFilePath("relative_path") |
| .build()) |
| .build(); |
| |
| mobileDataDownload |
| .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| mobileDataDownload |
| .downloadFileGroup( |
| DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // verify symlink structure, we can't get access to the full internal file uri, but we can tell |
| // the start of it |
| Uri expectedFileUri = |
| DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent()) |
| .buildUpon() |
| .appendPath(DirectoryUtil.MDD_STORAGE_SYMLINKS) |
| .appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS) |
| .appendPath(FILE_GROUP_NAME) |
| .build(); |
| // we can't get access to the full internal target file uri, but we know the start of it |
| Uri expectedStartTargetUri = |
| DirectoryUtil.getBaseDownloadDirectory(context, Optional.absent()) |
| .buildUpon() |
| .appendPath(DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS) |
| .appendPath("datadownloadfile_") |
| .build(); |
| |
| ClientFileGroup clientFileGroup = |
| mobileDataDownload |
| .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri()); |
| Uri targetUri = |
| AndroidUri.builder(context) |
| .fromAbsolutePath(readlink(adapter.toFile(fileUri).getAbsolutePath())) |
| .build(); |
| |
| assertThat(fileUri.toString()).contains(expectedFileUri.toString()); |
| assertThat(targetUri.toString()).contains(expectedStartTargetUri.toString()); |
| assertThat(fileStorage.exists(fileUri)).isTrue(); |
| assertThat(fileStorage.exists(targetUri)).isTrue(); |
| } |
| |
| @Test |
| public void remove_relativeFilePaths_removesSymlinks() throws Exception { |
| mobileDataDownload = |
| builderForTest() |
| .setFileDownloaderSupplier( |
| () -> |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR))) |
| .build(); |
| |
| DataFileGroup fileGroup = |
| DataFileGroup.newBuilder() |
| .setGroupName(FILE_GROUP_NAME) |
| .setOwnerPackage(context.getPackageName()) |
| .setPreserveFilenamesAndIsolateFiles(true) |
| .addFile( |
| DataFile.newBuilder() |
| .setFileId(FILE_ID) |
| .setByteSize(FILE_SIZE) |
| .setChecksumType(DataFile.ChecksumType.DEFAULT) |
| .setChecksum(FILE_CHECKSUM) |
| .setUrlToDownload(FILE_URL) |
| .setRelativeFilePath("relative_path") |
| .build()) |
| .build(); |
| |
| mobileDataDownload |
| .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(fileGroup).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| mobileDataDownload |
| .downloadFileGroup( |
| DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| ClientFileGroup clientFileGroup = |
| mobileDataDownload |
| .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| Uri fileUri = Uri.parse(clientFileGroup.getFile(0).getFileUri()); |
| |
| // Verify that file uri gets created |
| assertThat(fileStorage.exists(fileUri)).isTrue(); |
| |
| mobileDataDownload |
| .removeFileGroup(RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Verify that file uri still exists even though file group is stale |
| assertThat(fileStorage.exists(fileUri)).isTrue(); |
| |
| mobileDataDownload.maintenance().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Verify that file uri gets removed, once maintenance runs |
| if (flags.mddEnableGarbageCollection()) { |
| // cl/439051122 created a temporary FALSE override targeted to ASGA devices. This test only |
| // makes sense if the flag is true, but all_on testing doesn't respect diversion criteria in |
| // the launch. So we skip it for now. |
| // TODO(b/226551373): remove this once AsgaDisableMddLibGcLaunch is turned down |
| assertThat(fileStorage.exists(fileUri)).isFalse(); |
| } |
| } |
| |
| @Test |
| public void handleTask_duplicateInvocations_logsDownloadCompleteOnce() throws Exception { |
| // Override the feature flag to log at 100%. |
| flags.mddDefaultSampleInterval = Optional.of(1); |
| |
| TestFileDownloader testFileDownloader = |
| new TestFileDownloader( |
| TEST_DATA_RELATIVE_PATH, |
| fileStorage, |
| MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR)); |
| BlockingFileDownloader blockingFileDownloader = |
| new BlockingFileDownloader(DOWNLOAD_EXECUTOR, testFileDownloader); |
| |
| Supplier<FileDownloader> fakeFileDownloaderSupplier = () -> blockingFileDownloader; |
| |
| mobileDataDownload = |
| builderForTest().setFileDownloaderSupplier(fakeFileDownloaderSupplier).build(); |
| |
| // Use test populator to add the group as pending. |
| TestFileGroupPopulator populator = new TestFileGroupPopulator(context); |
| populator.refreshFileGroups(mobileDataDownload).get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| |
| // Call handle task in non-blocking way and use blocking file downloader to let handleTask1 wait |
| // at the download stage |
| ListenableFuture<Void> handleTask1Future = |
| mobileDataDownload.handleTask(TaskScheduler.WIFI_CHARGING_PERIODIC_TASK); |
| |
| blockingFileDownloader.waitForDownloadStarted(); |
| |
| ListenableFuture<Void> handleTask2Future = |
| mobileDataDownload.handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK); |
| |
| // Trigger a complete so the download "completes" after both tasks have been started. |
| blockingFileDownloader.finishDownloading(); |
| |
| // Wait for both futures to complete so we can make assertions about the events logged |
| handleTask2Future.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| handleTask1Future.get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| // Check that group is downloaded. |
| ClientFileGroup unused = getAndVerifyClientFileGroup(FILE_GROUP_NAME, 1); |
| |
| if (controlExecutorType.equals(ExecutorType.SINGLE_THREADED)) { |
| // Single-threaded executor step requires some time to allow logging to finish. |
| // TODO: Investigate whether TestingTaskBarrier can be used here to wait for |
| // executor become idle. |
| Thread.sleep(500); |
| } |
| |
| // Check that logger only logged 1 download complete event |
| ArgumentCaptor<MddLogData> logDataCompleteCaptor = ArgumentCaptor.forClass(MddLogData.class); |
| // 1007 is the tag number for MddClientEvent.Code.EVENT_CODE_UNSPECIFIED. |
| verify(mockLogger, times(1)).log(logDataCompleteCaptor.capture(), /* eventCode= */ eq(1007)); |
| } |
| |
| private MobileDataDownloadBuilder builderForTest() { |
| return MobileDataDownloadBuilder.newBuilder() |
| .setContext(context) |
| .setControlExecutor(controlExecutor) |
| .setTaskScheduler(Optional.of(mockTaskScheduler)) |
| .setLoggerOptional(Optional.of(mockLogger)) |
| .setDeltaDecoderOptional(Optional.absent()) |
| .setFileStorage(fileStorage) |
| .setNetworkUsageMonitor(networkUsageMonitor) |
| .setFlagsOptional(Optional.of(flags)); |
| } |
| |
| /** Creates MDD object and triggers handleTask to refresh and download file groups. */ |
| private void waitForHandleTask() |
| throws InterruptedException, ExecutionException, TimeoutException { |
| // This will trigger refreshing of FileGroupPopulators and downloading. |
| mobileDataDownload |
| .handleTask(TaskScheduler.CELLULAR_CHARGING_PERIODIC_TASK) |
| .get(MAX_HANDLE_TASK_WAIT_TIME_SECS, SECONDS); |
| |
| String debugString = mobileDataDownload.getDebugInfoAsString(); |
| Log.i(TAG, "MDD Lib dump:"); |
| for (String line : debugString.split("\n", -1)) { |
| Log.i(TAG, line); |
| } |
| } |
| |
| private ClientFileGroup getAndVerifyClientFileGroup(String fileGroupName, int fileCount) |
| throws ExecutionException, TimeoutException, InterruptedException { |
| ClientFileGroup clientFileGroup = |
| mobileDataDownload |
| .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(fileGroupName).build()) |
| .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); |
| assertThat(clientFileGroup).isNotNull(); |
| assertThat(clientFileGroup.getGroupName()).isEqualTo(fileGroupName); |
| assertThat(clientFileGroup.getFileCount()).isEqualTo(fileCount); |
| |
| return clientFileGroup; |
| } |
| |
| private void verifyClientFile(ClientFile clientFile, String fileId, int fileSize) |
| throws IOException { |
| assertThat(clientFile.getFileId()).isEqualTo(fileId); |
| Uri androidUri = Uri.parse(clientFile.getFileUri()); |
| assertThat(fileStorage.fileSize(androidUri)).isEqualTo(fileSize); |
| } |
| } |