| /* |
| * 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 com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR; |
| import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.util.concurrent.Futures.immediateVoidFuture; |
| import static org.junit.Assert.assertThrows; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.net.Uri; |
| import androidx.test.core.app.ApplicationProvider; |
| import com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints; |
| import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; |
| import com.google.android.libraries.mobiledatadownload.downloader.offroad.dagger.downloader2.BaseFileDownloaderModule; |
| 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.integration.downloader.SharedPreferencesDownloadMetadata; |
| import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; |
| 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.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.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.testing.junit.testparameterinjector.TestParameter; |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executors; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| |
| @RunWith(TestParameterInjector.class) |
| public class DownloadFileIntegrationTest { |
| |
| @Rule(order = 1) |
| public final MockitoRule mocks = MockitoJUnit.rule(); |
| |
| private static final String TAG = "DownloadFileIntegrationTest"; |
| |
| private static final long TIMEOUT_MS = 3000; |
| |
| private static final int FILE_SIZE = 554; |
| private static final String FILE_URL = "https://www.gstatic.com/suggest-dev/odws1_empty.jar"; |
| private static final String DOES_NOT_EXIST_FILE_URL = |
| "https://www.gstatic.com/non-existing/suggest-dev/not-exist.txt"; |
| |
| private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = |
| MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(2)); |
| |
| private static final Context context = ApplicationProvider.getApplicationContext(); |
| |
| private final Uri destinationFileUri = |
| AndroidUri.builder(context).setModule("mdd").setRelativePath("file_1").build(); |
| private final FakeTimeSource clock = new FakeTimeSource(); |
| private final TestFlags flags = new TestFlags(); |
| |
| private MobileDataDownload mobileDataDownload; |
| private DownloadProgressMonitor downloadProgressMonitor; |
| private SynchronousFileStorage fileStorage; |
| |
| private Supplier<FileDownloader> fileDownloaderSupplier; |
| private ListeningExecutorService controlExecutor; |
| |
| @Mock private SingleFileDownloadListener mockDownloadListener; |
| @Mock private NetworkUsageMonitor mockNetworkUsageMonitor; |
| |
| @TestParameter ExecutorType controlExecutorType; |
| |
| @Before |
| public void setUp() throws Exception { |
| // Set a default behavior for the download listener. |
| when(mockDownloadListener.onComplete()).thenReturn(immediateVoidFuture()); |
| |
| controlExecutor = controlExecutorType.executor(); |
| |
| downloadProgressMonitor = new DownloadProgressMonitor(clock, controlExecutor); |
| |
| fileStorage = |
| new SynchronousFileStorage( |
| /* backends= */ ImmutableList.of(AndroidFileBackend.builder(context).build()), |
| /* transforms= */ ImmutableList.of(), |
| /* monitors= */ ImmutableList.of(downloadProgressMonitor)); |
| |
| fileDownloaderSupplier = |
| () -> |
| BaseFileDownloaderModule.createOffroad2FileDownloader( |
| context, |
| DOWNLOAD_EXECUTOR, |
| controlExecutor, |
| fileStorage, |
| new SharedPreferencesDownloadMetadata( |
| context.getSharedPreferences("downloadmetadata", 0), controlExecutor), |
| Optional.of(downloadProgressMonitor), |
| /* urlEngineOptional= */ Optional.absent(), |
| /* exceptionHandlerOptional= */ Optional.absent(), |
| /* authTokenProviderOptional= */ Optional.absent(), |
| // /* cookieJarSupplierOptional= */ Optional.absent(), |
| /* trafficTag= */ Optional.absent(), |
| flags); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (fileStorage.exists(destinationFileUri)) { |
| fileStorage.deleteFile(destinationFileUri); |
| } |
| } |
| |
| @Test |
| public void downloadFile_success() throws Exception { |
| mobileDataDownload = builderForTest().build(); |
| |
| assertThat(fileStorage.exists(destinationFileUri)).isFalse(); |
| |
| SingleFileDownloadRequest downloadRequest = |
| SingleFileDownloadRequest.newBuilder() |
| .setDestinationFileUri(destinationFileUri) |
| .setUrlToDownload(FILE_URL) |
| .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) |
| .setListenerOptional(Optional.of(mockDownloadListener)) |
| .build(); |
| |
| ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); |
| downloadFuture.get(); |
| |
| // Verify the file is downloaded. |
| assertThat(fileStorage.exists(destinationFileUri)).isTrue(); |
| assertThat(fileStorage.fileSize(destinationFileUri)).isEqualTo(FILE_SIZE); |
| fileStorage.deleteFile(destinationFileUri); |
| |
| // Verify the downloadListener is called. |
| // Sleep for 1 sec to wait for the Future's callback to finish. |
| Thread.sleep(/* millis= */ 1000); |
| verify(mockDownloadListener).onComplete(); |
| } |
| |
| @Test |
| public void downloadFile_failure() throws Exception { |
| mobileDataDownload = builderForTest().build(); |
| |
| assertThat(fileStorage.exists(destinationFileUri)).isFalse(); |
| |
| // Trying to download doesn't exist URL. |
| SingleFileDownloadRequest downloadRequest = |
| SingleFileDownloadRequest.newBuilder() |
| .setDestinationFileUri(destinationFileUri) |
| .setUrlToDownload(DOES_NOT_EXIST_FILE_URL) |
| .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) |
| .setListenerOptional(Optional.of(mockDownloadListener)) |
| .build(); |
| |
| ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); |
| ExecutionException ex = assertThrows(ExecutionException.class, downloadFuture::get); |
| assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); |
| assertThat(((DownloadException) ex.getCause()).getDownloadResultCode()) |
| .isEqualTo(ANDROID_DOWNLOADER_HTTP_ERROR); |
| |
| // Verify the file is downloaded. |
| assertThat(fileStorage.exists(destinationFileUri)).isFalse(); |
| |
| // Verify the downloadListener is called. |
| // Sleep for 1 sec to wait for the Future's callback to finish. |
| Thread.sleep(/* millis= */ 1000); |
| verify(mockDownloadListener).onFailure(any(DownloadException.class)); |
| } |
| |
| @Test |
| public void downloadFile_cancel() throws Exception { |
| // Use a BlockingFileDownloader to ensure download remains in progress until it is cancelled. |
| BlockingFileDownloader blockingFileDownloader = new BlockingFileDownloader(DOWNLOAD_EXECUTOR); |
| |
| mobileDataDownload = |
| builderForTest().setFileDownloaderSupplier(() -> blockingFileDownloader).build(); |
| |
| SingleFileDownloadRequest downloadRequest = |
| SingleFileDownloadRequest.newBuilder() |
| .setDestinationFileUri(destinationFileUri) |
| .setUrlToDownload(FILE_URL) |
| .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) |
| .build(); |
| |
| ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); |
| |
| // Note: we could have a race condition here between when the FileDownloader.startDownloading() |
| // is called and when we cancel our download with Future.cancel(). To prevent this, we first |
| // wait until we have started downloading to ensure that it is in progress before we cancel. |
| blockingFileDownloader.waitForDownloadStarted(); |
| |
| // Cancel the download |
| downloadFuture.cancel(/* mayInterruptIfRunning= */ true); |
| |
| assertThrows(CancellationException.class, downloadFuture::get); |
| |
| // Cleanup |
| blockingFileDownloader.resetState(); |
| } |
| |
| /** |
| * Returns MDD Builder with common dependencies set -- additional dependencies are added in each |
| * test as needed. |
| */ |
| private MobileDataDownloadBuilder builderForTest() { |
| return MobileDataDownloadBuilder.newBuilder() |
| .setContext(context) |
| .setControlExecutor(controlExecutor) |
| .setFileDownloaderSupplier(fileDownloaderSupplier) |
| .setFileStorage(fileStorage) |
| .setDownloadMonitorOptional(Optional.of(downloadProgressMonitor)) |
| .setNetworkUsageMonitor(mockNetworkUsageMonitor) |
| .setFlagsOptional(Optional.of(flags)); |
| } |
| } |