/* * 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.internal; import static com.google.android.libraries.mobiledatadownload.internal.MddTestUtil.writeSharedFiles; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.accounts.Account; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import androidx.test.core.app.ApplicationProvider; import com.google.mobiledatadownload.internal.MetadataProto.DataFile; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupBookkeeping; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal; import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal.AllowedReaders; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.ActivatingCondition; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy; import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceStoragePolicy; import com.google.mobiledatadownload.internal.MetadataProto.ExtraHttpHeader; import com.google.mobiledatadownload.internal.MetadataProto.FileStatus; import com.google.mobiledatadownload.internal.MetadataProto.GroupKey; import com.google.mobiledatadownload.internal.MetadataProto.NewFileKey; import com.google.mobiledatadownload.internal.MetadataProto.SharedFile; import com.google.android.libraries.mobiledatadownload.AccountSource; import com.google.android.libraries.mobiledatadownload.AggregateException; import com.google.android.libraries.mobiledatadownload.DownloadException; import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode; import com.google.android.libraries.mobiledatadownload.FileSource; import com.google.android.libraries.mobiledatadownload.SilentFeedback; import com.google.android.libraries.mobiledatadownload.account.AccountUtil; import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; import com.google.android.libraries.mobiledatadownload.file.common.LimitExceededException; import com.google.android.libraries.mobiledatadownload.file.spi.Backend; import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager.GroupDownloadStatus; import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup; import com.google.android.libraries.mobiledatadownload.internal.downloader.DownloaderCallbackImpl; import com.google.android.libraries.mobiledatadownload.internal.downloader.MddFileDownloader; import com.google.android.libraries.mobiledatadownload.internal.experimentation.DownloadStageManager; import com.google.android.libraries.mobiledatadownload.internal.experimentation.NoOpDownloadStageManager; import com.google.android.libraries.mobiledatadownload.internal.logging.DownloadStateLogger; import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger; import com.google.android.libraries.mobiledatadownload.internal.logging.testing.FakeEventLogger; import com.google.android.libraries.mobiledatadownload.internal.util.DirectoryUtil; import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil; import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; 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.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.labs.concurrent.LabsFutures; import com.google.common.truth.Correspondence; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent; import com.google.mobiledatadownload.LogEnumsProto.MddDownloadResult; import com.google.mobiledatadownload.LogProto.DataDownloadFileGroupStats; import com.google.protobuf.Any; import com.google.protobuf.ByteString; import com.google.protobuf.ExtensionRegistryLite; import com.google.protobuf.StringValue; import java.io.Closeable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.Shadows; import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class FileGroupManagerTest { private static final long CURRENT_TIMESTAMP = 1000; private static final int TRAFFIC_TAG = 1000; private static final Executor SEQUENTIAL_CONTROL_EXECUTOR = Executors.newSingleThreadScheduledExecutor(); private static final String TEST_GROUP = "test-group"; private static final String TEST_GROUP_2 = "test-group-2"; private static final String TEST_GROUP_3 = "test-group-3"; private static final String TEST_GROUP_4 = "test-group-4"; private static final long FILE_GROUP_EXPIRATION_DATE_SECS = 10; private static final String HOST_APP_LOG_SOURCE = "HOST_APP_LOG_SOURCE"; private static final String HOST_APP_PRIMES_LOG_SOURCE = "HOST_APP_PRIMES_LOG_SOURCE"; private static final Correspondence GROUP_KEY_TO_VARIANT = Correspondence.transforming(GroupKey::getVariantId, "using variant"); private static final Correspondence KEY_GROUP_PAIR_TO_VARIANT = Correspondence.transforming( keyGroupPair -> { assertThat(keyGroupPair.groupKey().getVariantId()) .isEqualTo(keyGroupPair.dataFileGroup().getVariantId()); return keyGroupPair.dataFileGroup().getVariantId(); }, "using variant from group key and file group"); private static GroupKey testKey; private static GroupKey testKey2; private static GroupKey testKey3; private static GroupKey testKey4; private Context context; private FileGroupManager fileGroupManager; private FileGroupsMetadata fileGroupsMetadata; private SharedFileManager sharedFileManager; private SharedFilesMetadata sharedFilesMetadata; private FakeTimeSource testClock; private SynchronousFileStorage fileStorage; public File publicDirectory; private final TestFlags flags = new TestFlags(); @Rule(order = 2) public TemporaryFolder folder = new TemporaryFolder(); @Rule(order = 3) public final MockitoRule mocks = MockitoJUnit.rule(); @Mock EventLogger mockLogger; @Mock SilentFeedback mockSilentFeedback; @Mock MddFileDownloader mockDownloader; @Mock SharedFileManager mockSharedFileManager; @Mock FileGroupsMetadata mockFileGroupsMetadata; @Mock DownloadProgressMonitor mockDownloadMonitor; @Mock AccountSource mockAccountSource; @Mock Backend mockBackend; @Mock Closeable closeable; @Captor ArgumentCaptor fileSourceCaptor; @Captor ArgumentCaptor groupKeyCaptor; @Captor ArgumentCaptor> groupKeysCaptor; private DownloadStageManager downloadStageManager; @Before public void setUp() throws Exception { context = ApplicationProvider.getApplicationContext(); when(mockBackend.name()).thenReturn("blobstore"); fileStorage = new SynchronousFileStorage( Arrays.asList(AndroidFileBackend.builder(context).build(), mockBackend)); testClock = new FakeTimeSource().set(CURRENT_TIMESTAMP); testKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); testKey2 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .build(); testKey3 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_3) .setOwnerPackage(context.getPackageName()) .build(); testKey4 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_4) .setOwnerPackage(context.getPackageName()) .build(); fileGroupsMetadata = new SharedPreferencesFileGroupsMetadata( context, testClock, mockSilentFeedback, Optional.absent(), MoreExecutors.directExecutor()); sharedFilesMetadata = new SharedPreferencesSharedFilesMetadata( context, mockSilentFeedback, Optional.absent(), flags); sharedFileManager = new SharedFileManager( context, mockSilentFeedback, sharedFilesMetadata, fileStorage, mockDownloader, Optional.absent(), Optional.of(mockDownloadMonitor), mockLogger, flags, fileGroupsMetadata, Optional.absent(), MoreExecutors.directExecutor()); downloadStageManager = new NoOpDownloadStageManager(); fileGroupManager = new FileGroupManager( context, mockLogger, mockSilentFeedback, fileGroupsMetadata, sharedFileManager, testClock, Optional.of(mockAccountSource), SEQUENTIAL_CONTROL_EXECUTOR, Optional.absent(), fileStorage, downloadStageManager, flags); // TODO(b/117571083): Replace with fileStorage API. File downloadDirectory = new File(context.getFilesDir(), DirectoryUtil.MDD_STORAGE_MODULE + "/" + "shared"); publicDirectory = new File(downloadDirectory, DirectoryUtil.MDD_STORAGE_ALL_GOOGLE_APPS); publicDirectory.mkdirs(); // file sharing is available for SDK R+ ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.R); } private void assertLoggedNewConfigs( FakeEventLogger fakeEventLogger, DataDownloadFileGroupStats fileGroupStats, Void newConfigReceivedInfo) { ArrayListMultimap loggedConfigs = fakeEventLogger.getLoggedNewConfigReceived(); assertThat(loggedConfigs).hasSize(1); assertThat(loggedConfigs.get(fileGroupStats)).containsExactly(newConfigReceivedInfo); } @Test public void testAddGroupForDownload() throws Exception { FakeEventLogger fakeEventLogger = new FakeEventLogger(); resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Check that downloaded file groups doesn't contain this file group. assertThat(readDownloadedFileGroup(testKey)).isNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull(); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_correctlyPopulatesBuildIdAndVariantId() throws Exception { FakeEventLogger fakeEventLogger = new FakeEventLogger(); resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(10) .setVariantId("testVariant") .build(); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Check that downloaded file groups doesn't contain this file group. assertThat(readDownloadedFileGroup(testKey)).isNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull(); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_groupUpdated() throws Exception { FakeEventLogger fakeEventLogger = new FakeEventLogger(); resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); // Update the file id and see that the group gets updated in the pending groups list. dataFileGroup = dataFileGroup.toBuilder() .setFile(0, dataFileGroup.getFile(0).toBuilder().setFileId("file2")) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); // Update other parameters and check that we successfully add the group. dataFileGroup = dataFileGroup.toBuilder().setFileGroupVersionNumber(2).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); dataFileGroup = dataFileGroup.toBuilder().setStaleLifetimeSecs(50).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); dataFileGroup = dataFileGroup.toBuilder() .setDownloadConditions( DownloadConditions.newBuilder() .setDeviceNetworkPolicy( DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK)) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); DownloadConditions downloadConditions = DownloadConditions.newBuilder() .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_LOWER_THRESHOLD) .build(); dataFileGroup = dataFileGroup.toBuilder().setDownloadConditions(downloadConditions).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); fakeEventLogger.reset(); dataFileGroup = dataFileGroup.toBuilder() .setAllowedReadersEnum(AllowedReaders.ONLY_GOOGLE_PLAY_SERVICES) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_groupUpdated_whenBuildChanges() throws Exception { FakeEventLogger fakeEventLogger = new FakeEventLogger(); resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Reset to clear events before next add group call fakeEventLogger.reset(); // Update the file id and see that the group gets updated in the pending groups list. dataFileGroup = dataFileGroup.toBuilder().setBuildId(123456789L).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_groupUpdated_whenVariantChanges() throws Exception { FakeEventLogger fakeEventLogger = new FakeEventLogger(); resetFileGroupManager(fakeEventLogger, fileGroupsMetadata, sharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Reset to clear events before next add group call fakeEventLogger.reset(); // Update the file id and see that the group gets updated in the pending groups list. dataFileGroup = dataFileGroup.toBuilder().setVariantId("some-different-variant").build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); assertLoggedNewConfigs( fakeEventLogger, createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownloadWithSyncId_failedToUpdateMetadataNoScheduleViaSpe() throws Exception { // Mock FileGroupsMetadata and SharedFileManager to test failure scenario. resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternalWithDownloadId(TEST_GROUP, 2); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); when(mockSharedFileManager.reserveFileEntry(any(NewFileKey.class))) .thenReturn(Futures.immediateFuture(true)); // Failed to write to Metadata, no task will be scheduled via SPE. when(mockFileGroupsMetadata.write(any(GroupKey.class), any(DataFileGroupInternal.class))) .thenReturn(Futures.immediateFuture(false)); when(mockFileGroupsMetadata.read(any(GroupKey.class))) .thenReturn(Futures.immediateFuture(null)); ListenableFuture addGroupFuture = fileGroupManager.addGroupForDownload(testKey, dataFileGroup); assertThrows(ExecutionException.class, addGroupFuture::get); IOException e = LabsFutures.getFailureCauseAs(addGroupFuture, IOException.class); assertThat(e).hasMessageThat().contains("Failed to commit new group metadata to disk."); // Check that downloaded file groups doesn't contain this file group. GroupKey downloadedkey = testKey.toBuilder().setDownloaded(true).build(); assertWithMessage(String.format("Expected that key %s should not exist.", downloadedkey)) .that(mockFileGroupsMetadata.read(downloadedkey).get()) .isNull(); // Check that the get method doesn't return this file group. assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull(); verify(mockSharedFileManager).reserveFileEntry(groupKeys[0]); verify(mockSharedFileManager).reserveFileEntry(groupKeys[1]); } @Test public void testAddGroupForDownload_duplicatePendingGroup() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Send the exact same group again, and check that it is considered duplicate. assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isFalse(); } @Test public void testAddGroupForDownload_duplicateDownloadedGroup() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writeDownloadedFileGroup(testKey, dataFileGroup); // Send the exact same group as the downloaded group, and check that it is considered duplicate. assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isFalse(); verifyNoInteractions(mockLogger); } @Test public void testAddGroupForDownload_filePropertiesUpdated() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, dataFileGroup); dataFileGroup = dataFileGroup.toBuilder() .setFile(0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://file2")) .build(); // Send the same group with different property, and check that it is NOT duplicate. assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); dataFileGroup = dataFileGroup.toBuilder() .setFile(0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://file3")) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); } @Test public void testAddGroupForDownload_differentPendingGroup_duplicateDownloadedGroup() throws Exception { DataFileGroupInternal firstGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); assertThat(fileGroupManager.addGroupForDownload(testKey, firstGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, firstGroup, CURRENT_TIMESTAMP); verify(mockLogger) .logNewConfigReceived(createFileGroupDetails(firstGroup).clearFileCount().build(), null); reset(mockLogger); // Create a second group that is identical except for one different file id. DataFileGroupInternal.Builder secondGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder(); secondGroup.setFile(0, secondGroup.getFile(0).toBuilder().setFileId("file2")); writeDownloadedFileGroup(testKey, secondGroup.build()); // Send the updated group, and check that it is not considered duplicate. assertThat(fileGroupManager.addGroupForDownload(testKey, secondGroup.build()).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, secondGroup.build(), CURRENT_TIMESTAMP); verify(mockLogger) .logNewConfigReceived(createFileGroupDetails(firstGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_subscribeFailed() throws Exception { // Mock SharedFileManager to test failure scenario. resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(NewFileKey.class); when(mockSharedFileManager.reserveFileEntry(fileCaptor.capture())) .thenReturn( Futures.immediateFuture(true), Futures.immediateFuture(false), Futures.immediateFuture(true)); ExecutionException ex = assertThrows( ExecutionException.class, fileGroupManager.addGroupForDownload(testKey, dataFileGroup)::get); assertThat(ex).hasCauseThat().isInstanceOf(IOException.class); // Verify that we tried to subscribe to only the first 2 files. assertThat(fileCaptor.getAllValues()).containsExactly(groupKeys[0], groupKeys[1]); verify(mockLogger) .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_subscribeFailed_firstFile() throws Exception { // Mock SharedFileManager to test failure scenario. resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(NewFileKey.class); when(mockSharedFileManager.reserveFileEntry(fileCaptor.capture())) .thenReturn( Futures.immediateFuture(false), Futures.immediateFuture(true), Futures.immediateFuture(true)); ExecutionException ex = assertThrows( ExecutionException.class, fileGroupManager.addGroupForDownload(testKey, dataFileGroup)::get); assertThat(ex).hasCauseThat().isInstanceOf(IOException.class); // Verify that we tried to subscribe to only the first file. assertThat(fileCaptor.getAllValues()).containsExactly(groupKeys[0]); verify(mockLogger) .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_alreadyDownloadedGroup() throws Exception { // Write a group to the pending shared prefs. DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, pendingGroup); DataFileGroupInternal oldDownloadedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); writeDownloadedFileGroup(testKey, oldDownloadedGroup); // Add a newer version of that group DataFileGroupInternal receivedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3); assertThat(fileGroupManager.addGroupForDownload(testKey, receivedGroup).get()).isTrue(); // The new added group should be the pending group. verifyAddGroupForDownloadWritesMetadata(testKey, receivedGroup, CURRENT_TIMESTAMP); assertThat(oldDownloadedGroup).isEqualTo(readDownloadedFileGroup(testKey)); } @Test public void testAddGroupForDownload_addEmptyGroup() throws Exception { // Write a group to the pending shared prefs. DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, pendingGroup); DataFileGroupInternal emptyGroup = DataFileGroupInternal.newBuilder().setGroupName(TEST_GROUP).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, emptyGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, emptyGroup, CURRENT_TIMESTAMP); } @Test public void testAddGroupForDownload_addGroupForUninstalledApp() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); GroupKey uninstalledAppKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).setOwnerPackage("not.installed.app").build(); // Send a group with an owner package that is not installed. Ensure that this group is rejected. assertThrows( UninstalledAppException.class, () -> fileGroupManager.addGroupForDownload(uninstalledAppKey, dataFileGroup)); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verifyNoMoreInteractions(mockLogger); } @Test public void testAddGroupForDownload_expiredGroup() throws Exception { Calendar date = new Calendar.Builder().setDate(1970, Calendar.JANUARY, 2).build(); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setExpirationDateSecs(date.getTimeInMillis() / 1000) .build(); testClock.set(System.currentTimeMillis()); // Send a group with an expiration date that has already passed. assertThrows( ExpiredFileGroupException.class, () -> fileGroupManager.addGroupForDownload(testKey, dataFileGroup)); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verifyNoMoreInteractions(mockLogger); } @Test public void testAddGroupForDownload_justExpiredGroup() throws Exception { long oneHourAgo = (System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)) / 1000; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setExpirationDateSecs(oneHourAgo) .build(); testClock.set(System.currentTimeMillis()); // Send a group with an expiration date that has already passed. assertThrows( ExpiredFileGroupException.class, () -> fileGroupManager.addGroupForDownload(testKey, dataFileGroup)); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verifyNoMoreInteractions(mockLogger); } @Test public void testAddGroupForDownload_nonexpiredGroup() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup); long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000; dataFileGroup = dataFileGroup.toBuilder().setExpirationDateSecs(tenDaysFromNow).build(); testClock.set(System.currentTimeMillis()); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, testClock.currentTimeMillis()); // Check that downloaded file groups doesn't contain this file group. assertThat(readDownloadedFileGroup(testKey)).isNull(); // Check that the get method doesn't return this file group. assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull(); verify(mockLogger) .logNewConfigReceived(createFileGroupDetails(dataFileGroup).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_nonexpiredGroupNoExpiration() throws Exception { DataFileGroupInternal.Builder dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder(); NewFileKey[] groupKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(dataFileGroup.build()); dataFileGroup.setExpirationDateSecs(0); // 0 means don't expire testClock.set(System.currentTimeMillis()); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata( testKey, dataFileGroup.build(), testClock.currentTimeMillis()); // Check that downloaded file groups doesn't contain this file group. assertThat(readDownloadedFileGroup(testKey)).isNull(); // Check that the get method doesn't return this file group. assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[0]).get()).isNotNull(); assertThat(sharedFileManager.getSharedFile(groupKeys[1]).get()).isNotNull(); verify(mockLogger) .logNewConfigReceived( createFileGroupDetails(dataFileGroup.build()).clearFileCount().build(), null); } @Test public void testAddGroupForDownload_extendExpiration() throws Exception { DataFileGroupInternal.Builder dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder(); long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000; dataFileGroup.setExpirationDateSecs(tenDaysFromNow); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), CURRENT_TIMESTAMP); // Now send the group again with a longer expiration. long twentyDaysFromNow = tenDaysFromNow + TimeUnit.DAYS.toSeconds(10); dataFileGroup = dataFileGroup.setExpirationDateSecs(twentyDaysFromNow); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), CURRENT_TIMESTAMP); } @Test public void testAddGroupForDownload_reduceExpiration() throws Exception { long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setExpirationDateSecs(tenDaysFromNow) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); // Now send the group again with a longer expiration. long fiveDaysFromNow = tenDaysFromNow - TimeUnit.DAYS.toSeconds(5); dataFileGroup = dataFileGroup.toBuilder().setExpirationDateSecs(fiveDaysFromNow).build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, CURRENT_TIMESTAMP); } @Test public void testAddGroupForDownload_delayedDownload() throws Exception { flags.enableDelayedDownload = Optional.of(true); // Create 2 groups, one of which requires device side activation. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions( DownloadConditions.newBuilder() .setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED)) .build(); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3); // Assert that adding the first group throws an exception. ExecutionException ex = assertThrows( ExecutionException.class, fileGroupManager.addGroupForDownload(testKey, fileGroup1)::get); assertThat(ex).hasCauseThat().isInstanceOf(ActivationRequiredForGroupException.class); assertThat(fileGroupManager.addGroupForDownload(testKey2, fileGroup2).get()).isTrue(); // Now activate the group and verify that we are able to add the first group. assertThat(fileGroupManager.setGroupActivation(testKey, true).get()).isTrue(); assertThat(fileGroupManager.addGroupForDownload(testKey, fileGroup1).get()).isTrue(); // Deactivate the group again and verify that we should no longer be able to add it. assertThat(fileGroupManager.setGroupActivation(testKey, false).get()).isTrue(); ex = assertThrows( ExecutionException.class, fileGroupManager.addGroupForDownload(testKey, fileGroup1)::get); assertThat(ex).hasCauseThat().isInstanceOf(ActivationRequiredForGroupException.class); } @Test public void testAddGroupForDownload_onWifiFirst() throws Exception { int elapsedTime = 1000; DataFileGroupInternal.Builder dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder(); { testClock.set(elapsedTime); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); // The wifi only download timestamp is set correctly. verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } { // Update metadata does not change the wifi only download timestamp. long tenDaysFromNow = (System.currentTimeMillis() + TimeUnit.DAYS.toMillis(10)) / 1000; dataFileGroup.setExpirationDateSecs(tenDaysFromNow); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } { // Change another metadata field does not change the wifi only download timestamp. dataFileGroup.setFileGroupVersionNumber(2); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); // The wifi only download timestamp does not change. verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } { // Update the file's urlToDownload will reset the wifi only download timestamp. elapsedTime = 2000; testClock.set(elapsedTime); dataFileGroup.setFile( 0, dataFileGroup.getFile(0).toBuilder().setUrlToDownload("https://new_url")); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); // The wifi only download timestamp change since we change the urlToDownload verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } { // Update the file's byteSize will reset the wifi only download timestamp. elapsedTime = 3000; testClock.set(elapsedTime); dataFileGroup.setFile(1, dataFileGroup.getFile(1).toBuilder().setByteSize(5001)); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); // The wifi only download timestamp change since we change the urlToDownload verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } { // Update the file's checksum will reset the wifi only download timestamp. elapsedTime = 4000; testClock.set(elapsedTime); dataFileGroup.setFile(1, dataFileGroup.getFile(1).toBuilder().setChecksum("new check sum")); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup.build()).get()) .isTrue(); // The wifi only download timestamp change since we change the urlToDownload verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup.build(), elapsedTime); } } @Test public void testAddGroupForDownload_addsSideloadedGroup() throws Exception { // Create sideloaded group DataFileGroupInternal sideloadedGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .addFile( DataFile.newBuilder() .setFileId("sideloaded_file") .setUrlToDownload("file:/test") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, sideloadedGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, sideloadedGroup, 1000L); } @Test public void testAddGroupForDownload_multipleVariants() throws Exception { // Create 3 group keys of the same group, but with different variants GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build(); DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build(); assertThat(fileGroupManager.addGroupForDownload(defaultGroupKey, defaultFileGroup).get()) .isTrue(); assertThat(fileGroupManager.addGroupForDownload(enGroupKey, enFileGroup).get()).isTrue(); assertThat(fileGroupManager.addGroupForDownload(frGroupKey, frFileGroup).get()).isTrue(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("", "en", "fr"); } @Test public void removeFileGroup_noVersionExists() throws Exception { // No record for both pending key and downloaded key. GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty(); // There is no pending file group, so no call to clearSyncReasons. verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_pendingVersionExists() throws Exception { DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build(); DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build(); NewFileKey newFileKey1 = SharedFilesMetadata.createKeyFromDataFile(dataFile1, AllowedReaders.ALL_GOOGLE_APPS); NewFileKey newFileKey2 = SharedFilesMetadata.createKeyFromDataFile(dataFile2, AllowedReaders.ALL_GOOGLE_APPS); DataFileGroupInternal pendingFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(1) .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addAllFile(Lists.newArrayList(dataFile1, dataFile2)) .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); writePendingFileGroup(pendingGroupKey, pendingFileGroup); writeSharedFiles( sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readPendingFileGroup(downloadedGroupKey)).isNull(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty(); Uri pendingFileUri1 = DirectoryUtil.getOnDeviceUri( context, newFileKey1.getAllowedReaders(), dataFile1.getFileId(), newFileKey1.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); Uri pendingFileUri2 = DirectoryUtil.getOnDeviceUri( context, newFileKey2.getAllowedReaders(), dataFile2.getFileId(), newFileKey2.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); verify(mockDownloader).stopDownloading(newFileKey1.getChecksum(), pendingFileUri1); verify(mockDownloader).stopDownloading(newFileKey2.getChecksum(), pendingFileUri2); verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_downloadedVersionExists() throws Exception { DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build(); DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build(); DataFileGroupInternal downloadedFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(0) .setBuildId(0) .setVariantId("") .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addAllFile(Lists.newArrayList(dataFile1, dataFile2)) .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup); writeSharedFiles( sharedFilesMetadata, downloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readPendingFileGroup(downloadedGroupKey)).isNull(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()) .containsExactly( downloadedFileGroup.toBuilder() .setBookkeeping( downloadedFileGroup.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build()); verify(mockDownloader, never()).stopDownloading(any(String.class), any(Uri.class)); verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_bothVersionsExist() throws Exception { DataFile registeredFile = DataFile.newBuilder().setFileId("file").setChecksum("registered").build(); DataFile downloadedFile = DataFile.newBuilder().setFileId("file").setChecksum("downloaded").build(); NewFileKey registeredFileKey = SharedFilesMetadata.createKeyFromDataFile(registeredFile, AllowedReaders.ALL_GOOGLE_APPS); DataFileGroupInternal pendingFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(1) .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addFile(registeredFile) .build(); DataFileGroupInternal downloadedFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(0) .setBuildId(0) .setVariantId("") .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addFile(downloadedFile) .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); writePendingFileGroup(pendingGroupKey, pendingFileGroup); writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup); writeSharedFiles( sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, downloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readPendingFileGroup(downloadedGroupKey)).isNull(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()) .containsExactly( downloadedFileGroup.toBuilder() .setBookkeeping( downloadedFileGroup.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build()); Uri pendingFileUri = DirectoryUtil.getOnDeviceUri( context, registeredFileKey.getAllowedReaders(), registeredFile.getFileId(), registeredFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); // Only called once to stop download of pending file. verify(mockDownloader).stopDownloading(registeredFileKey.getChecksum(), pendingFileUri); verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_bothVersionsExist_onlyRemovePending() throws Exception { DataFile registeredFile = DataFile.newBuilder().setFileId("file").setChecksum("registered").build(); DataFile downloadedFile = DataFile.newBuilder().setFileId("file").setChecksum("downloaded").build(); NewFileKey registeredFileKey = SharedFilesMetadata.createKeyFromDataFile(registeredFile, AllowedReaders.ALL_GOOGLE_APPS); DataFileGroupInternal pendingFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(1) .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addFile(registeredFile) .build(); DataFileGroupInternal downloadedFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(0) .setBuildId(0) .setVariantId("") .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addFile(downloadedFile) .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); writePendingFileGroup(pendingGroupKey, pendingFileGroup); writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup); writeSharedFiles( sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, downloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ true).get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readPendingFileGroup(downloadedGroupKey)).isNull(); // Pending group was just removed, and downloaded was not added to stale groups. assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); // Downloaded group is still available. assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .containsExactly(GroupKeyAndGroup.create(downloadedGroupKey, downloadedFileGroup)); Uri pendingFileUri = DirectoryUtil.getOnDeviceUri( context, registeredFileKey.getAllowedReaders(), registeredFile.getFileId(), registeredFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); // Only called once to stop download of pending file. verify(mockDownloader).stopDownloading(registeredFileKey.getChecksum(), pendingFileUri); verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_fileReferencedByOtherFileGroup_willNotCancelDownload() throws Exception { DataFile dataFile1 = DataFile.newBuilder().setFileId("file1").setChecksum("checksum1").build(); DataFile dataFile2 = DataFile.newBuilder().setFileId("file2").setChecksum("checksum2").build(); DataFileGroupInternal pendingFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(1) .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addAllFile(Lists.newArrayList(dataFile1, dataFile2)) .build(); DataFileGroupInternal pendingFileGroup2 = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP_2) .setFileGroupVersionNumber(1) .setAllowedReadersEnum(AllowedReaders.ALL_GOOGLE_APPS) .addAllFile(Lists.newArrayList(dataFile1, dataFile2)) .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); GroupKey groupKey2 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey2 = groupKey2.toBuilder().setDownloaded(false).build(); writePendingFileGroup(pendingGroupKey, pendingFileGroup); writePendingFileGroup(pendingGroupKey2, pendingFileGroup2); writeSharedFiles( sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingFileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .containsExactly( GroupKeyAndGroup.create(pendingGroupKey, pendingFileGroup), GroupKeyAndGroup.create(pendingGroupKey2, pendingFileGroup2)); fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readPendingFileGroup(pendingGroupKey2)).isNotNull(); assertThat(readPendingFileGroup(downloadedGroupKey)).isNull(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .containsExactly(GroupKeyAndGroup.create(pendingGroupKey2, pendingFileGroup2)); verify(mockDownloader, never()).stopDownloading(any(String.class), any(Uri.class)); verifyNoInteractions(mockLogger); } @Test public void removeFileGroup_onFailure() throws Exception { // Mock FileGroupsMetadata to test failure scenario. resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager); DataFileGroupInternal pendingFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(1) .build(); DataFileGroupInternal downloadedFileGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .setFileGroupVersionNumber(0) .setBuildId(0) .setVariantId("") .build(); GroupKey groupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey pendingGroupKey = groupKey.toBuilder().setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKey.toBuilder().setDownloaded(true).build(); when(mockFileGroupsMetadata.read(pendingGroupKey)) .thenReturn(Futures.immediateFuture(pendingFileGroup)); when(mockFileGroupsMetadata.read(downloadedGroupKey)) .thenReturn(Futures.immediateFuture(downloadedFileGroup)); when(mockFileGroupsMetadata.remove(pendingGroupKey)).thenReturn(Futures.immediateFuture(true)); when(mockFileGroupsMetadata.remove(downloadedGroupKey)) .thenReturn(Futures.immediateFuture(false)); // Exception should be thrown when fileGroupManager attempts to remove downloadedGroupKey. ExecutionException expected = assertThrows( ExecutionException.class, () -> fileGroupManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get()); assertThat(expected).hasCauseThat().isInstanceOf(IOException.class); verify(mockFileGroupsMetadata).remove(pendingGroupKey); verify(mockFileGroupsMetadata).remove(downloadedGroupKey); verify(mockFileGroupsMetadata, never()).addStaleGroup(any(DataFileGroupInternal.class)); verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED); } @Test public void removeFileGroup_removesSideloadedGroup() throws Exception { // Create sideloaded group DataFileGroupInternal sideloadedGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .addFile( DataFile.newBuilder() .setFileId("sideloaded_file") .setUrlToDownload("file:/test") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .build(); writePendingFileGroup(testKey, sideloadedGroup); writeDownloadedFileGroup(testKey, sideloadedGroup); fileGroupManager.removeFileGroup(testKey, /* pendingOnly= */ false).get(); assertThat(readPendingFileGroup(testKey)).isNull(); assertThat(readDownloadedFileGroup(testKey)).isNull(); } @Test public void removeFileGroup_whenMultipleVariantsExist_whenNoVariantSpecified_removesEmptyVariantGroup() throws Exception { // Create 3 variants of a group (default (no variant), en, fr) and have them all added. When // removeFileGroups is called and the group key given does not include a variant, ensure that // the default group is removed. GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build(); DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build(); writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup); writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup); writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup); writeSharedFiles( sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); // Assert that all file groups share the same file even through the variants are different assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); { // Perfrom removal once and check that the default group gets removed fileGroupManager.removeFileGroup(defaultGroupKey, /* pendingOnly= */ false).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("en", "fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("en", "fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } { // Perform remove again and verify that there is no change in state fileGroupManager.removeFileGroup(defaultGroupKey, /* pendingOnly= */ false).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("en", "fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("en", "fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } } @Test public void removeFileGroup_whenMultipleVariantsExist_whenVariantSpecified_removesVariantGroup() throws Exception { // Create 3 variants of a group (default (no variant), en, fr) and have them all added. When // removeFileGroups is called and the group key given includes a variant, ensure that only // the group with that variant is removed. GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build(); DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build(); writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup); writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup); writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup); writeSharedFiles( sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); // Assert that all file groups share the same file even through the variants are different assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); { // Perfrom removal once and check that the en group gets removed fileGroupManager.removeFileGroup(enGroupKey, /* pendingOnly= */ false).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("", "fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("", "fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } { // Perform remove again and verify that there is no change in state fileGroupManager.removeFileGroup(enGroupKey, /* pendingOnly= */ false).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("", "fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("", "fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } } @Test public void testRemoveFileGroups_whenNoGroupsExist_performsNoRemovals() throws Exception { GroupKey groupKey1 = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey groupKey2 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .build(); fileGroupManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()).isEmpty(); } @Test public void testRemoveFileGroups_whenNoMatchingKeysExist_performsNoRemovals() throws Exception { // Create a pending and downloaded version of a file group // Pending group includes 2 files: 1 that is shared with downloaded group and one that will be // marked pending DataFileGroupInternal pendingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); DataFileGroupInternal downloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setGroupName(TEST_GROUP).setOwnerPackage(context.getPackageName()); GroupKey pendingGroupKey = groupKeyBuilder.setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKeyBuilder.setDownloaded(true).build(); GroupKey nonMatchingGroupKey1 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build(); GroupKey nonMatchingGroupKey2 = groupKeyBuilder.setGroupName(TEST_GROUP_3).build(); // Write file group and shared file metadata // NOTE: pending group contains all files in downloaded group, so we only need to write shared // file state once. writePendingFileGroup(pendingGroupKey, pendingFileGroup); writeDownloadedFileGroup(downloadedGroupKey, downloadedFileGroup); writeSharedFiles( sharedFilesMetadata, pendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager .removeFileGroups(ImmutableList.of(nonMatchingGroupKey1, nonMatchingGroupKey2)) .get(); assertThat(readPendingFileGroup(pendingGroupKey)).isNotNull(); assertThat(readDownloadedFileGroup(downloadedGroupKey)).isNotNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()).hasSize(2); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); verify(mockDownloader, times(0)).stopDownloading(any(), any()); } @Test public void testRemoveFileGroups_whenMatchingPendingGroups_performsRemove() throws Exception { // Create 2 pending groups that will be removed, 1 pending group that shouldn't be removed, and // 1 downloaded group that shouldn't be removed DataFileGroupInternal pendingGroupToRemove1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder().build(); DataFileGroupInternal pendingGroupToRemove2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); DataFileGroupInternal pendingGroupToKeep = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1).toBuilder().build(); DataFileGroupInternal downloadedGroupToKeep = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_4, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(false); GroupKey pendingGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build(); GroupKey pendingGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build(); GroupKey pendingGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_3).build(); GroupKey downloadedGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_4).setDownloaded(true).build(); writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1); writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2); writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep); writeDownloadedFileGroup(downloadedGroupKeyToKeep, downloadedGroupToKeep); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove1, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove2, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, downloadedGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKeyToRemove1, pendingGroupKeyToRemove2)) .get(); // Construct Pending File Uris to check which downloads were stopped NewFileKey pendingFileKey1 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove1)[0]; NewFileKey pendingFileKey2 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove2)[0]; NewFileKey pendingFileKey3 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToKeep)[0]; Uri pendingFileUri1 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey1.getAllowedReaders(), pendingGroupToRemove1.getFile(0).getFileId(), pendingFileKey1.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); Uri pendingFileUri2 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey2.getAllowedReaders(), pendingGroupToRemove2.getFile(0).getFileId(), pendingFileKey2.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); Uri pendingFileUri3 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey3.getAllowedReaders(), pendingGroupToKeep.getFile(0).getFileId(), pendingFileKey3.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); // Assert that matching pending groups are removed assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull(); assertThat(readDownloadedFileGroup(downloadedGroupKeyToKeep)).isNotNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()).hasSize(2); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).isEmpty(); verify(mockDownloader).stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1); verify(mockDownloader).stopDownloading(pendingFileKey2.getChecksum(), pendingFileUri2); verify(mockDownloader, times(0)) .stopDownloading(pendingFileKey3.getChecksum(), pendingFileUri3); } @Test public void testRemoveFileGroups_whenMatchingDownloadedGroups_performsRemove() throws Exception { // Create 2 downloaded groups that will be removed, 1 downloaded group that shouldn't be // removed, and 1 pending group that shouldn't be removed DataFileGroupInternal downloadedGroupToRemove1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal downloadedGroupToRemove2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); DataFileGroupInternal downloadedGroupToKeep = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1); DataFileGroupInternal pendingGroupToKeep = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_4, 1).toBuilder().build(); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(true); GroupKey downloadedGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build(); GroupKey downloadedGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build(); GroupKey downloadedGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_3).build(); GroupKey pendingGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP_4).setDownloaded(false).build(); writeDownloadedFileGroup(downloadedGroupKeyToRemove1, downloadedGroupToRemove1); writeDownloadedFileGroup(downloadedGroupKeyToRemove2, downloadedGroupToRemove2); writeDownloadedFileGroup(downloadedGroupKeyToKeep, downloadedGroupToKeep); writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep); writeSharedFiles( sharedFilesMetadata, downloadedGroupToRemove1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, downloadedGroupToRemove2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, downloadedGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, pendingGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager .removeFileGroups( ImmutableList.of(downloadedGroupKeyToRemove1, downloadedGroupKeyToRemove2)) .get(); // Construct Pending File Uri to check that it isn't cancelled NewFileKey pendingFileKey1 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToKeep)[0]; Uri pendingFileUri1 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey1.getAllowedReaders(), pendingGroupToKeep.getFile(0).getFileId(), pendingFileKey1.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); // Assert that matching pending groups are removed assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove1)).isNull(); assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove2)).isNull(); assertThat(readDownloadedFileGroup(downloadedGroupKeyToKeep)).isNotNull(); assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .containsExactly( GroupKeyAndGroup.create(downloadedGroupKeyToKeep, downloadedGroupToKeep), GroupKeyAndGroup.create(pendingGroupKeyToKeep, pendingGroupToKeep)); assertThat(fileGroupsMetadata.getAllStaleGroups().get()) .containsExactly( downloadedGroupToRemove1.toBuilder() .setBookkeeping( downloadedGroupToRemove1.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build(), downloadedGroupToRemove2.toBuilder() .setBookkeeping( downloadedGroupToRemove2.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build()); verify(mockDownloader, times(0)) .stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1); } @Test public void testRemoveFileGroups_whenMatchingBothVersions_performsRemove() throws Exception { // Create 2 file groups, each with 2 versions (downloaded and pending) DataFileGroupInternal downloadedGroupToRemove1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal downloadedGroupToRemove2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); DataFileGroupInternal pendingGroupToRemove1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder().build(); DataFileGroupInternal pendingGroupToRemove2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(true); GroupKey downloadedGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).build(); GroupKey downloadedGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build(); GroupKey pendingGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build(); GroupKey pendingGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(false).build(); writeDownloadedFileGroup(downloadedGroupKeyToRemove1, downloadedGroupToRemove1); writeDownloadedFileGroup(downloadedGroupKeyToRemove2, downloadedGroupToRemove2); writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1); writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2); writeSharedFiles( sharedFilesMetadata, downloadedGroupToRemove1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, downloadedGroupToRemove2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove1, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove2, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); // NOTE: the downloaded version of keys are used in this call, but this shouldn't be relevant; // both downloaded and pending versions of group keys should be checked for removal. fileGroupManager .removeFileGroups( ImmutableList.of(downloadedGroupKeyToRemove1, downloadedGroupKeyToRemove2)) .get(); // Construct Pending File Uri to check that its download was cancelled NewFileKey pendingFileKey1 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove1)[0]; NewFileKey pendingFileKey2 = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroupToRemove2)[0]; Uri pendingFileUri1 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey1.getAllowedReaders(), pendingGroupToRemove1.getFile(0).getFileId(), pendingFileKey1.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); Uri pendingFileUri2 = DirectoryUtil.getOnDeviceUri( context, pendingFileKey2.getAllowedReaders(), pendingGroupToRemove2.getFile(0).getFileId(), pendingFileKey2.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); // Assert that matching pending groups are removed assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove1)).isNull(); assertThat(readDownloadedFileGroup(downloadedGroupKeyToRemove2)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()).isEmpty(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()) .containsExactly( downloadedGroupToRemove1.toBuilder() .setBookkeeping( downloadedGroupToRemove1.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build(), downloadedGroupToRemove2.toBuilder() .setBookkeeping( downloadedGroupToRemove2.getBookkeeping().toBuilder() .setStaleExpirationDate(1) .build()) .build()); verify(mockDownloader, times(1)) .stopDownloading(pendingFileKey1.getChecksum(), pendingFileUri1); verify(mockDownloader, times(1)) .stopDownloading(pendingFileKey2.getChecksum(), pendingFileUri2); } @Test public void testRemoveFileGroups_whenFilesAreReferencedByOtherGroups_doesNotCancelDownloads() throws Exception { // Setup 2 pending groups to remove that each contain a file referenced by a 3rd pending group // that doesn't get removed. The pending file downloads referenced by the 3rd group should not // be cancelled. DataFileGroupInternal pendingGroupToKeep = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); DataFileGroupInternal pendingGroupToRemove1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1).toBuilder() .addFile(pendingGroupToKeep.getFile(0)) .build(); DataFileGroupInternal pendingGroupToRemove2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 1).toBuilder() .addFile(pendingGroupToKeep.getFile(1)) .build(); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()).setDownloaded(false); GroupKey pendingGroupKeyToKeep = groupKeyBuilder.setGroupName(TEST_GROUP).build(); GroupKey pendingGroupKeyToRemove1 = groupKeyBuilder.setGroupName(TEST_GROUP_2).build(); GroupKey pendingGroupKeyToRemove2 = groupKeyBuilder.setGroupName(TEST_GROUP_3).build(); writePendingFileGroup(pendingGroupKeyToKeep, pendingGroupToKeep); writePendingFileGroup(pendingGroupKeyToRemove1, pendingGroupToRemove1); writePendingFileGroup(pendingGroupKeyToRemove2, pendingGroupToRemove2); writeSharedFiles( sharedFilesMetadata, pendingGroupToKeep, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove1, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, pendingGroupToRemove2, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKeyToRemove1, pendingGroupKeyToRemove2)) .get(); assertThat(readPendingFileGroup(pendingGroupKeyToRemove1)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToRemove2)).isNull(); assertThat(readPendingFileGroup(pendingGroupKeyToKeep)).isNotNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .containsExactly(GroupKeyAndGroup.create(pendingGroupKeyToKeep, pendingGroupToKeep)); // Get On Device Uris to check if file downloads were cancelled List uncancelledFileUris = getOnDeviceUrisForFileGroup(pendingGroupToKeep); verify(mockDownloader, times(0)).stopDownloading(any(), eq(uncancelledFileUris.get(0))); verify(mockDownloader, times(0)).stopDownloading(any(), eq(uncancelledFileUris.get(1))); verify(mockDownloader, times(1)) .stopDownloading( pendingGroupToRemove1.getFile(0).getChecksum(), getOnDeviceUrisForFileGroup(pendingGroupToRemove1).get(0)); verify(mockDownloader, times(1)) .stopDownloading( pendingGroupToRemove2.getFile(0).getChecksum(), getOnDeviceUrisForFileGroup(pendingGroupToRemove2).get(0)); } @Test public void testRemoveFileGroups_whenRemovePendingGroupFails_doesNotContinue() throws Exception { // Use Mocks to simulate failure scenario resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()); GroupKey pendingGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build(); when(mockFileGroupsMetadata.read(pendingGroupKey)) .thenReturn(Futures.immediateFuture(pendingGroup)); when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey))) .thenReturn(Futures.immediateFuture(null)); when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture())) .thenReturn(Futures.immediateFuture(false)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey)) .get()); assertThat(ex).hasCauseThat().isInstanceOf(IOException.class); verify(mockFileGroupsMetadata, times(0)).addStaleGroup(any()); verify(mockSharedFileManager, times(0)).cancelDownload(any()); verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED); verify(mockFileGroupsMetadata, times(1)).removeAllGroupsWithKeys(any()); List attemptedRemoveKeys = groupKeysCaptor.getValue(); assertThat(attemptedRemoveKeys).containsExactly(pendingGroupKey); } @Test public void testRemoveFileGroups_whenRemoveDownloadedGroupFails_doesNotContinue() throws Exception { // Use Mock FileGroupsMetadata to simulate failure scenario resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal downloadedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()); GroupKey pendingGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build(); // Mock variations of group key reads when(mockFileGroupsMetadata.read(pendingGroupKey)) .thenReturn(Futures.immediateFuture(pendingGroup)); when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey))) .thenReturn(Futures.immediateFuture(null)); when(mockFileGroupsMetadata.read(downloadedGroupKey)) .thenReturn(Futures.immediateFuture(downloadedGroup)); when(mockFileGroupsMetadata.read(getDownloadedKey(pendingGroupKey))) .thenReturn(Futures.immediateFuture(null)); // Return true for pending groups removed, but false for downloaded groups when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture())) .thenReturn(Futures.immediateFuture(true)) .thenReturn(Futures.immediateFuture(false)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey)) .get()); assertThat(ex).hasCauseThat().isInstanceOf(IOException.class); verify(mockFileGroupsMetadata, times(0)).addStaleGroup(any()); verify(mockSharedFileManager, times(0)).cancelDownload(any()); verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED); verify(mockFileGroupsMetadata, times(2)).removeAllGroupsWithKeys(any()); List> removeCallInvocations = groupKeysCaptor.getAllValues(); assertThat(removeCallInvocations.get(0)).containsExactly(pendingGroupKey); assertThat(removeCallInvocations.get(1)).containsExactly(downloadedGroupKey); } @Test public void testRemoveFileGroups_whenAddingStaleGroupFails_doesNotContinue() throws Exception { // Use Mock FileGroupsMetadata to simulate failure scenario resetFileGroupManager(mockFileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal downloadedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()); GroupKey pendingGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build(); // Mock read group key variations when(mockFileGroupsMetadata.read(pendingGroupKey)) .thenReturn(Futures.immediateFuture(pendingGroup)); when(mockFileGroupsMetadata.read(getPendingKey(downloadedGroupKey))) .thenReturn(Futures.immediateFuture(null)); when(mockFileGroupsMetadata.read(downloadedGroupKey)) .thenReturn(Futures.immediateFuture(downloadedGroup)); when(mockFileGroupsMetadata.read(getDownloadedKey(pendingGroupKey))) .thenReturn(Futures.immediateFuture(null)); // Always return true for remove calls when(mockFileGroupsMetadata.removeAllGroupsWithKeys(groupKeysCaptor.capture())) .thenReturn(Futures.immediateFuture(true)); // Fail when attempting to add a stale group when(mockFileGroupsMetadata.addStaleGroup(downloadedGroup)) .thenReturn(Futures.immediateFuture(false)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey)) .get()); assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class); verify(mockFileGroupsMetadata, times(1)).addStaleGroup(downloadedGroup); verify(mockSharedFileManager, times(0)).cancelDownload(any()); verify(mockLogger, times(1)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED); verify(mockFileGroupsMetadata, times(2)).removeAllGroupsWithKeys(any()); List> removeCallInvocations = groupKeysCaptor.getAllValues(); assertThat(removeCallInvocations.get(0)).containsExactly(pendingGroupKey); assertThat(removeCallInvocations.get(1)).containsExactly(downloadedGroupKey); } @Test public void testRemoveFileGroups_whenCancellingPendingDownloadFails_doesNotContinue() throws Exception { // Use Mock FileGroupsMetadata to simulate failure scenario resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal downloadedGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); GroupKey.Builder groupKeyBuilder = GroupKey.newBuilder().setOwnerPackage(context.getPackageName()); GroupKey pendingGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP).setDownloaded(false).build(); GroupKey downloadedGroupKey = groupKeyBuilder.setGroupName(TEST_GROUP_2).setDownloaded(true).build(); writePendingFileGroup(pendingGroupKey, pendingGroup); writeDownloadedFileGroup(downloadedGroupKey, downloadedGroup); writeSharedFiles( sharedFilesMetadata, pendingGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, downloadedGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); // Fail when cancelling download NewFileKey[] pendingFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(pendingGroup); when(mockSharedFileManager.cancelDownload(pendingFileKeys[0])) .thenReturn(Futures.immediateFailedFuture(new Exception("Test failure"))); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .removeFileGroups(ImmutableList.of(pendingGroupKey, downloadedGroupKey)) .get()); assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class); assertThat(readPendingFileGroup(pendingGroupKey)).isNull(); assertThat(readDownloadedFileGroup(downloadedGroupKey)).isNull(); assertThat(fileGroupsMetadata.getAllFreshGroups().get()).isEmpty(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()) .containsExactly( downloadedGroup.toBuilder() .setBookkeeping( downloadedGroup.getBookkeeping().toBuilder().setStaleExpirationDate(1).build()) .build()); } @Test public void testRemoveFileGroups_whenMultipleVariantsExists_removesVariantsSpecified() throws Exception { // Create multiple variants of a group (default (empty), en, fr) and remove the default (empty) // variant and en keys. Ensure that only the fr group remains. GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); GroupKey frGroupKey = defaultGroupKey.toBuilder().setVariantId("fr").build(); DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); DataFileGroupInternal frFileGroup = defaultFileGroup.toBuilder().setVariantId("fr").build(); writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup); writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup); writePendingFileGroup(getPendingKey(frGroupKey), frFileGroup); writeSharedFiles( sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, frFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); // Assert that all file groups share the same file even through the variants are different assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); { // Perfrom removal once and check that the correct groups get removed fileGroupManager.removeFileGroups(ImmutableList.of(defaultGroupKey, enGroupKey)).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } { // Perform remove again and verify that there is no change in state fileGroupManager.removeFileGroups(ImmutableList.of(defaultGroupKey, enGroupKey)).get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .comparingElementsUsing(GROUP_KEY_TO_VARIANT) .containsExactly("fr"); assertThat(fileGroupsMetadata.getAllFreshGroups().get()) .comparingElementsUsing(KEY_GROUP_PAIR_TO_VARIANT) .containsExactly("fr"); assertThat(sharedFilesMetadata.getAllFileKeys().get()).hasSize(1); } } @Test public void testGetDownloadedGroup() throws Exception { assertThat(fileGroupManager.getFileGroup(testKey, true).get()).isNull(); DataFileGroupInternal dataFileGroup = MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP, 2); writeDownloadedFileGroup(testKey, dataFileGroup); DataFileGroupInternal downloadedGroup = fileGroupManager.getFileGroup(testKey, true).get(); MddTestUtil.assertMessageEquals(dataFileGroup, downloadedGroup); } @Test public void testGetDownloadedGroup_whenMultipleVariantsExists_getsCorrectGroup() throws Exception { GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); // Initially, assert that groups don't exist assertThat(fileGroupManager.getFileGroup(defaultGroupKey, true).get()).isNull(); assertThat(fileGroupManager.getFileGroup(enGroupKey, true).get()).isNull(); // Create groups and write them DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); writeDownloadedFileGroup(getDownloadedKey(defaultGroupKey), defaultFileGroup); writeDownloadedFileGroup(getDownloadedKey(enGroupKey), enFileGroup); // Assert the correct group is returned for each key DataFileGroupInternal groupForDefaultKey = fileGroupManager.getFileGroup(defaultGroupKey, true).get(); MddTestUtil.assertMessageEquals(defaultFileGroup, groupForDefaultKey); DataFileGroupInternal groupForEnKey = fileGroupManager.getFileGroup(enGroupKey, true).get(); MddTestUtil.assertMessageEquals(enFileGroup, groupForEnKey); } @Test public void testGetPendingGroup() throws Exception { assertThat(fileGroupManager.getFileGroup(testKey, false).get()).isNull(); DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, dataFileGroup); DataFileGroupInternal pendingGroup = fileGroupManager.getFileGroup(testKey, false).get(); MddTestUtil.assertMessageEquals(dataFileGroup, pendingGroup); } @Test public void testGetPendingGroup_whenMultipleVariantsExists_getsCorrectGroup() throws Exception { GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); // Initially, assert that groups don't exist assertThat(fileGroupManager.getFileGroup(defaultGroupKey, false).get()).isNull(); assertThat(fileGroupManager.getFileGroup(enGroupKey, false).get()).isNull(); // Create groups and write them DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal enFileGroup = defaultFileGroup.toBuilder().setVariantId("en").build(); writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup); writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup); // Assert the correct group is returned for each key DataFileGroupInternal groupForDefaultKey = fileGroupManager.getFileGroup(defaultGroupKey, false).get(); MddTestUtil.assertMessageEquals(defaultFileGroup, groupForDefaultKey); DataFileGroupInternal groupForEnKey = fileGroupManager.getFileGroup(enGroupKey, false).get(); MddTestUtil.assertMessageEquals(enFileGroup, groupForEnKey); } @Test public void testSetGroupActivation_deactivationRemovesGroupsRequiringActivation() throws Exception { flags.enableDelayedDownload = Optional.of(true); // Create 2 groups, one of which requires device side activation. DataFileGroupInternal.Builder fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder(); DownloadConditions.Builder downloadConditions = DownloadConditions.newBuilder() .setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED); fileGroup1.setDownloadConditions(downloadConditions); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3); // Activate both group keys and add groups to FileGroupManager. assertThat(fileGroupManager.setGroupActivation(testKey, true).get()).isTrue(); assertThat(fileGroupManager.addGroupForDownload(testKey, fileGroup1.build()).get()).isTrue(); assertThat(fileGroupManager.setGroupActivation(testKey2, true).get()).isTrue(); assertThat(fileGroupManager.addGroupForDownload(testKey2, fileGroup2).get()).isTrue(); // Add a downloaded version of the second group, that requires device side activation. DataFileGroupInternal downloadedfileGroup2 = MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP_2, 1); downloadConditions = DownloadConditions.newBuilder(); downloadedfileGroup2 = downloadedfileGroup2.toBuilder() .setDownloadConditions( downloadConditions.setActivatingCondition(ActivatingCondition.DEVICE_ACTIVATED)) .build(); writeDownloadedFileGroup(testKey2, downloadedfileGroup2); // Deactivate both group keys, and check that the groups that required activation are deleted. assertThat(fileGroupManager.setGroupActivation(testKey, false).get()).isTrue(); // Setting group activation to false will only remove groups that have // ActivatingCondition.DEVICE_ACTIVATED. So the pending version will remain, while the // downloaded one is removed. assertThat(fileGroupManager.setGroupActivation(testKey2, false).get()).isTrue(); assertThat(readPendingFileGroup(testKey)).isNull(); assertThat(readPendingFileGroup(testKey2)).isNotNull(); assertThat(readDownloadedFileGroup(testKey2)).isNull(); } @Test public void testImportFilesIntoFileGroup_whenExistingGroupDoesNotExist_fails() throws Exception { DataFile inlineFile = DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build(); ImmutableList updatedDataFileList = ImmutableList.of(inlineFile); GroupKey groupKey = GroupKey.newBuilder().setGroupName("non-existing-group").build(); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", updatedDataFileList, /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenExistingPendingGroupDoesNotMatchIdentifiers_fails() throws Exception { // Set up existing pending file group DataFileGroupInternal existingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()) .build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), existingFileGroup); writeSharedFiles( sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 1, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenExistingDownloadedGroupDoesNotMatchIdentifiers_fails() throws Exception { // Set up existing downloaded file group DataFileGroupInternal existingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()) .build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingFileGroup); writeSharedFiles( sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "testvariant", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenBuildIdDoesNotMatch_fails() throws Exception { // Set up existing pending/downloaded groups and check that they do not match due to build ID // differences. // Any can pack proto messages only, so use StringValue. Any customProperty = Any.parseFrom( StringValue.of("testCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setBuildId(1) .setVariantId("testvariant") .setCustomProperty(customProperty) .build(); DataFileGroupInternal existingPendingFileGroup = existingDownloadedFileGroup.toBuilder().setBuildId(2).build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 3, /* variantId= */ "testvariant", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.of(customProperty), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenVariantIdDoesNotMatch_fails() throws Exception { // Set up existing pending/downloaded groups and check that they do not match due to variant ID // differences. // Any can pack proto messages only, so use StringValue. Any customProperty = Any.parseFrom( StringValue.of("testCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setBuildId(1) .setVariantId("testvariant") .setCustomProperty(customProperty) .build(); DataFileGroupInternal existingPendingFileGroup = existingDownloadedFileGroup.toBuilder().setVariantId("testvariant2").build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 1, /* variantId= */ "testvariant3", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.of(customProperty), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToMismatch_fails() throws Exception { // Set up existing pending/downloaded groups and check that they do not match due to custom // property differences. // Any can pack proto messages only, so use StringValue. Any downloadedCustomProperty = Any.parseFrom( StringValue.of("testDownloadedCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); Any pendingCustomProperty = Any.parseFrom( StringValue.of("testPendingCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); Any mismatchedCustomProperty = Any.parseFrom( StringValue.of("testMismatcheCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setBuildId(1) .setVariantId("testvariant") .setCustomProperty(downloadedCustomProperty) .build(); DataFileGroupInternal existingPendingFileGroup = existingDownloadedFileGroup.toBuilder().setCustomProperty(pendingCustomProperty).build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 1, /* variantId= */ "testvariant", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.of(mismatchedCustomProperty), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenCustomPropertyDoesNotMatch_whenDueToBeingAbsent_fails() throws Exception { // Set up existing pending/downloaded groups and check that they do not match due to custom // property differences. // Any can pack proto messages only, so use StringValue. Any downloadedCustomProperty = Any.parseFrom( StringValue.of("testDownloadedCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); Any pendingCustomProperty = Any.parseFrom( StringValue.of("testPendingCustomProperty").toByteString(), ExtensionRegistryLite.getEmptyRegistry()); DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setBuildId(1) .setVariantId("testvariant") .setCustomProperty(downloadedCustomProperty) .build(); DataFileGroupInternal existingPendingFileGroup = existingDownloadedFileGroup.toBuilder().setCustomProperty(pendingCustomProperty).build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 1, /* variantId= */ "testvariant", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()).isEqualTo(DownloadResultCode.GROUP_NOT_FOUND_ERROR); } @Test public void testImportFilesIntoFileGroup_whenUnableToReserveNewFiles_fails() throws Exception { // Reset with mock SharedFileManager to force failures resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); // Create existing file group and a new file group that will add an inline file (which needs to // be reserved in SharedFileManager). DataFileGroupInternal existingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingFileGroup); writeSharedFiles( sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); NewFileKey newInlineFileKey = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), existingFileGroup.getAllowedReadersEnum()); NewFileKey existingFileKey = SharedFilesMetadata.createKeyFromDataFile( existingFileGroup.getFile(0), existingFileGroup.getAllowedReadersEnum()); when(mockSharedFileManager.reserveFileEntry(newInlineFileKey)) .thenReturn(Futures.immediateFuture(false)); when(mockSharedFileManager.reserveFileEntry(existingFileKey)) .thenReturn(Futures.immediateFuture(true)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ updatedDataFileList, /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) ex.getCause(); assertThat(dex.getDownloadResultCode()) .isEqualTo(DownloadResultCode.UNABLE_TO_RESERVE_FILE_ENTRY); } @Test public void testImportFilesIntoFileGroup_whenNoNewInlineFilesSpecifiedAndFilesDownloaded_completes() throws Exception { // Create a group that has 1 standard file and 1 inline file, both downloaded DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()) .build(); ImmutableMap inlineFileMap = ImmutableMap.of( "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), dataFileGroup); writeSharedFiles( sharedFilesMetadata, dataFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Since no new files were specified, the group should remain the same (downloaded). DataFileGroupInternal downloadedFileGroup = fileGroupsMetadata.read(getDownloadedKey(groupKey)).get(); assertThat(downloadedFileGroup.getGroupName()).isEqualTo(dataFileGroup.getGroupName()); assertThat(downloadedFileGroup.getBuildId()).isEqualTo(dataFileGroup.getBuildId()); assertThat(downloadedFileGroup.getVariantId()).isEqualTo(dataFileGroup.getVariantId()); assertThat(downloadedFileGroup.getFileList()) .containsExactlyElementsIn(dataFileGroup.getFileList()); } @Test public void testImportFilesIntoFileGroup_whenNoNewFilesSpecifiedAndFilesPending_completes() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), dataFileGroup); writeSharedFiles( sharedFilesMetadata, dataFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Since no new files were specified, the group should remain the same (pending). DataFileGroupInternal pendingFileGroup = fileGroupsMetadata.read(getPendingKey(groupKey)).get(); assertThat(pendingFileGroup.getGroupName()).isEqualTo(dataFileGroup.getGroupName()); assertThat(pendingFileGroup.getBuildId()).isEqualTo(dataFileGroup.getBuildId()); assertThat(pendingFileGroup.getVariantId()).isEqualTo(dataFileGroup.getVariantId()); assertThat(pendingFileGroup.getFileList()).isEqualTo(dataFileGroup.getFileList()); } @Test public void testImportFilesIntoFileGroup_whenImportingInlineFileAndPending_mergesGroup() throws Exception { // Set up an existing pending file group and a new inline file to merge DataFileGroupInternal existingPendingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()); ImmutableMap inlineFileMap = ImmutableMap.of( "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); writeSharedFiles( sharedFilesMetadata, existingPendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); // TODO: remove once SFM can perform import // write inline file as downloaded so FGM can find it NewFileKey newInlineFileKey = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), existingPendingFileGroup.getAllowedReadersEnum()); SharedFile newInlineSharedFile = SharedFile.newBuilder() .setFileName(updatedDataFileList.get(0).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get(); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", updatedDataFileList, inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that resulting file group remains pending, but should have both files merged together DataFileGroupInternal pendingFileGroupAfterImport = fileGroupsMetadata.read(getPendingKey(groupKey)).get(); assertThat(pendingFileGroupAfterImport.getFileCount()).isEqualTo(2); assertThat(pendingFileGroupAfterImport.getFileList()) .containsExactly(existingPendingFileGroup.getFile(0), updatedDataFileList.get(0)); } @Test public void testImportFilesIntoFileGroup_whenImportingInlineFileAndDownloaded_mergesGroup() throws Exception { // Set up an existing downloaded file group and a new file group with an inline file to merge DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()); ImmutableMap inlineFileMap = ImmutableMap.of( "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writeSharedFiles( sharedFilesMetadata, existingDownloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); // TODO: remove once SFM can perform import // write inline file as downloaded so FGM can find it NewFileKey newInlineFileKey = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), existingDownloadedFileGroup.getAllowedReadersEnum()); SharedFile newInlineSharedFile = SharedFile.newBuilder() .setFileName(updatedDataFileList.get(0).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get(); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", updatedDataFileList, inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that resulting file group is downloaded, but should have both files merged together DataFileGroupInternal downloadedFileGroupAfterImport = fileGroupsMetadata.read(getDownloadedKey(groupKey)).get(); assertThat(downloadedFileGroupAfterImport.getFileCount()).isEqualTo(2); assertThat(downloadedFileGroupAfterImport.getFileList()) .containsExactly(existingDownloadedFileGroup.getFile(0), updatedDataFileList.get(0)); } @Test public void testImportFilesIntoFileGroup_whenMatchesDownloadedButNotPending_importsToDownloaded() throws Exception { // Set up an existing pending file group, an existing downloaded file group and a new file // group that matches the downloaded file group DataFileGroupInternal existingPendingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal existingDownloadedFileGroup = existingPendingFileGroup.toBuilder() .clearFile() .addFile(MddTestUtil.createDataFile("downloaded-file", 0)) .setBuildId(10) .build(); ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:abc") .build()); ImmutableMap inlineFileMap = ImmutableMap.of( "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writeSharedFiles( sharedFilesMetadata, existingPendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, existingDownloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); // TODO: remove once SFM can perform import // write inline file as downloaded so FGM can find it NewFileKey newInlineFileKey = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), existingDownloadedFileGroup.getAllowedReadersEnum()); SharedFile newInlineSharedFile = SharedFile.newBuilder() .setFileName(updatedDataFileList.get(0).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get(); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 10, /* variantId= */ "", updatedDataFileList, inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that downloaded file group now contains the merged file group and pending group remains // the same. DataFileGroupInternal downloadedFileGroupAfterImport = fileGroupsMetadata.read(getDownloadedKey(groupKey)).get(); assertThat(downloadedFileGroupAfterImport.getBuildId()) .isEqualTo(existingDownloadedFileGroup.getBuildId()); assertThat(downloadedFileGroupAfterImport.getFileCount()).isEqualTo(2); assertThat(downloadedFileGroupAfterImport.getFileList()) .containsExactly(existingDownloadedFileGroup.getFile(0), updatedDataFileList.get(0)); assertThat(fileGroupsMetadata.read(getPendingKey(groupKey)).get()) .isEqualTo(existingPendingFileGroup); } @Test public void testImportFilesIntoFileGroup_whenMatchesPendingButNotDownloaded_importsToPending() throws Exception { // Set up an existing pending file group, an existing downloaded file group and a new file // group that matches the pending file group DataFileGroupInternal existingPendingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); DataFileGroupInternal existingDownloadedFileGroup = existingPendingFileGroup.toBuilder() .clearFile() .addFile(MddTestUtil.createDataFile("downloaded-file", 0)) .setBuildId(10) .build(); ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:abc") .build()); ImmutableMap inlineFileMap = ImmutableMap.of( "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), existingPendingFileGroup); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); writeSharedFiles( sharedFilesMetadata, existingPendingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, existingDownloadedFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); // TODO: remove once SFM can perform import // write inline file as downloaded so FGM can find it NewFileKey newInlineFileKey = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), existingPendingFileGroup.getAllowedReadersEnum()); SharedFile newInlineSharedFile = SharedFile.newBuilder() .setFileName(updatedDataFileList.get(0).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(newInlineFileKey, newInlineSharedFile).get(); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", updatedDataFileList, inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that pending file group now contains the merged file group and downloaded group remains // the same. DataFileGroupInternal pendingFileGroupAfterImport = fileGroupsMetadata.read(getPendingKey(groupKey)).get(); assertThat(pendingFileGroupAfterImport.getBuildId()) .isEqualTo(existingPendingFileGroup.getBuildId()); assertThat(pendingFileGroupAfterImport.getFileCount()).isEqualTo(2); assertThat(pendingFileGroupAfterImport.getFileList()) .containsExactly(existingPendingFileGroup.getFile(0), updatedDataFileList.get(0)); assertThat(fileGroupsMetadata.read(getDownloadedKey(groupKey)).get()) .isEqualTo(existingDownloadedFileGroup); } @Test public void testImportFilesIntoFileGroup_whenPerformingImport_choosesFileSourceById() throws Exception { // Use mockSharedFileManager to check startImport call resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); FileSource testFileSource = FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE")); // Setup file group with inline file to import DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build()) .build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); NewFileKey inlineFileKey = SharedFilesMetadata.createKeyFromDataFile( fileGroup.getFile(0), fileGroup.getAllowedReadersEnum()); writePendingFileGroup(getPendingKey(groupKey), fileGroup); // Setup mock SFM with successful calls when(mockSharedFileManager.reserveFileEntry(inlineFileKey)) .thenReturn(Futures.immediateFuture(true)); when(mockSharedFileManager.getFileStatus(inlineFileKey)) .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_IN_PROGRESS)); when(mockSharedFileManager.startImport( groupKeyCaptor.capture(), eq(fileGroup.getFile(0)), eq(inlineFileKey), any(), fileSourceCaptor.capture())) .thenReturn(Futures.immediateVoidFuture()); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of("inline-file", testFileSource), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that SFM startImport was called with expected inputs verify(mockSharedFileManager, times(1)).startImport(any(), any(), any(), any(), any()); assertThat(groupKeyCaptor.getValue().getGroupName()).isEqualTo(TEST_GROUP); assertThat(fileSourceCaptor.getValue()).isEqualTo(testFileSource); } @Test public void testImportFilesIntoFileGroup_whenFileAlreadyDownloaded_doesNotAttemptToImport() throws Exception { // Use mockSharedFileManager to check startImport call resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); FileSource testFileSource = FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE")); // Create an existing downloaded file group and attempt to import again with a given source. // Since the file is already marked DOWNLOAD_COMPLETE, the import should not be invoked. DataFileGroupInternal existingDownloadedFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:abc") .build()) .build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); NewFileKey inlineFileKey = SharedFilesMetadata.createKeyFromDataFile( existingDownloadedFileGroup.getFile(0), existingDownloadedFileGroup.getAllowedReadersEnum()); writeDownloadedFileGroup(getDownloadedKey(groupKey), existingDownloadedFileGroup); // Setup mock SFM with successful calls when(mockSharedFileManager.reserveFileEntry(inlineFileKey)) .thenReturn(Futures.immediateFuture(true)); when(mockSharedFileManager.getFileStatus(inlineFileKey)) .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of("inline-file", testFileSource), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); // Check that SFM startImport was not called verify(mockSharedFileManager, times(0)).startImport(any(), any(), any(), any(), any()); } @Test public void testImportFilesIntoFileGroups_whenFileSourceNotProvided_fails() throws Exception { // create a file group added to MDD with an inline file and check that import call fails if // source is not provided. DataFileGroupInternal existingFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile( DataFile.newBuilder() .setFileId("inline-file") .setChecksum("abc") .setUrlToDownload("inlinefile:abc") .build()) .build(); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); writePendingFileGroup(getPendingKey(groupKey), existingFileGroup); writeSharedFiles( sharedFilesMetadata, existingFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS)); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", /* updatedDataFileList= */ ImmutableList.of(), /* inlineFileMap= */ ImmutableMap.of(), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException aex = (AggregateException) ex.getCause(); assertThat(aex.getFailures()).hasSize(1); assertThat(aex.getFailures().get(0)).isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) aex.getFailures().get(0); assertThat(dex.getDownloadResultCode()) .isEqualTo(DownloadResultCode.MISSING_INLINE_FILE_SOURCE); } @Test public void testImportFilesIntoFileGroup_whenImportFails_preventsMetadataUpdate() throws Exception { // Use mockSharedFileManager to mock a failure for an import resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); FileSource testFileSource1 = FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE_1")); FileSource testFileSource2 = FileSource.ofByteString(ByteString.copyFromUtf8("TEST_FILE_SOURCE_2")); // Setup empty file group DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); // Setup list of files that should be imported ImmutableList updatedDataFileList = ImmutableList.of( DataFile.newBuilder() .setFileId("inline-file-1") .setChecksum("abc") .setUrlToDownload("inlinefile:sha1:abc") .build(), DataFile.newBuilder() .setFileId("inline-file-2") .setChecksum("def") .setUrlToDownload("inlinefile:sha1:def") .build()); GroupKey groupKey = GroupKey.newBuilder().setGroupName(TEST_GROUP).build(); NewFileKey inlineFileKey1 = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(0), fileGroup.getAllowedReadersEnum()); NewFileKey inlineFileKey2 = SharedFilesMetadata.createKeyFromDataFile( updatedDataFileList.get(1), fileGroup.getAllowedReadersEnum()); writeDownloadedFileGroup(getDownloadedKey(groupKey), fileGroup); // Setup mock calls to SFM when(mockSharedFileManager.reserveFileEntry(any())).thenReturn(Futures.immediateFuture(true)); // Mock that inline file 1 completed, but inline file 2 failed when(mockSharedFileManager.getFileStatus(inlineFileKey1)) .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_IN_PROGRESS)); when(mockSharedFileManager.getFileStatus(inlineFileKey2)) .thenReturn(Futures.immediateFuture(FileStatus.DOWNLOAD_FAILED)); when(mockSharedFileManager.startImport( any(), eq(updatedDataFileList.get(0)), eq(inlineFileKey1), any(), any())) .thenReturn(Futures.immediateVoidFuture()); when(mockSharedFileManager.startImport( any(), eq(updatedDataFileList.get(1)), eq(inlineFileKey2), any(), any())) .thenReturn( Futures.immediateFailedFuture( DownloadException.builder() .setDownloadResultCode(DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR) .build())); ExecutionException ex = assertThrows( ExecutionException.class, () -> fileGroupManager .importFilesIntoFileGroup( groupKey, /* buildId= */ 0, /* variantId= */ "", updatedDataFileList, /* inlineFileMap= */ ImmutableMap.of( "inline-file-1", testFileSource1, "inline-file-2", testFileSource2), /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get()); // Check for expected cause assertThat(ex).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException aex = (AggregateException) ex.getCause(); assertThat(aex.getFailures()).hasSize(1); assertThat(aex.getFailures().get(0)).isInstanceOf(DownloadException.class); DownloadException dex = (DownloadException) aex.getFailures().get(0); assertThat(dex.getDownloadResultCode()) .isEqualTo(DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR); // Check that existing (empty) group remains in metadata. iow, the files from // updatedDataFileList were not added since the import failed. DataFileGroupInternal existingFileGroup = fileGroupsMetadata.read(getDownloadedKey(groupKey)).get(); assertThat(existingFileGroup.getFileList()).isEmpty(); // Check that SFM startImport was called with expected inputs verify(mockSharedFileManager, times(2)).startImport(any(), any(), any(), any(), any()); } @Test public void testImportFilesIntoFileGroup_skipsSideloadedFile() throws Exception { // Create sideloaded group with inline file DataFileGroupInternal sideloadedGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .addFile( DataFile.newBuilder() .setFileId("sideloaded_file") .setUrlToDownload("file:/test") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .addFile( DataFile.newBuilder() .setFileId("inline_file") .setUrlToDownload("inlinefile:sha1:checksum") .setChecksum("checksum") .build()) .build(); ImmutableMap inlineFileMap = ImmutableMap.of( "inline_file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT"))); NewFileKey inlineFileKey = SharedFilesMetadata.createKeyFromDataFile( sideloadedGroup.getFile(1), sideloadedGroup.getAllowedReadersEnum()); // Write group as pending since we are waiting on inline file writePendingFileGroup(testKey, sideloadedGroup); // Write inline file as succeeded so we skip SFM's import call SharedFile inlineSharedFile = SharedFile.newBuilder() .setFileName(sideloadedGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(inlineFileKey, inlineSharedFile).get(); fileGroupManager .importFilesIntoFileGroup( testKey, sideloadedGroup.getBuildId(), sideloadedGroup.getVariantId(), /* updatedDataFileList= */ ImmutableList.of(), inlineFileMap, /* customPropertyOptional= */ Optional.absent(), noCustomValidation()) .get(); assertThat(readPendingFileGroup(testKey)).isNull(); assertThat(readDownloadedFileGroup(testKey)).isNotNull(); } @Test public void testDownloadPendingGroup_success() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = createDataFileGroup( TEST_GROUP, /* fileCount= */ 2, /* downloadAttemptCount= */ 3, /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.SUCCESS, createFileGroupDetails(fileGroup) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); verify(mockLogger) .logMddDownloadLatency( createFileGroupDetails(fileGroup).build(), createMddDownloadLatency( /* downloadAttemptCount= */ 4, /* downloadLatencyMs= */ 0L, /* totalLatencyMs= */ 500L)); } @Test public void testDownloadPendingGroup_withFailingCustomValidator() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = createDataFileGroup( TEST_GROUP, /* fileCount= */ 2, /* downloadAttemptCount= */ 3, /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); AsyncFunction failingValidator = unused -> Futures.immediateFuture(false); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), failingValidator); ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); assertThat(exception).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException cause = (DownloadException) exception.getCause(); assertThat(cause).isNotNull(); assertThat(cause).hasMessageThat().contains("CUSTOM_FILEGROUP_VALIDATION_FAILED"); // Verify that pending key was removed. This will ensure the files are eligible for garbage // collection. assertThat(readPendingFileGroup(testKey)).isNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); ArgumentCaptor resultCodeCaptor = ArgumentCaptor.forClass(MddDownloadResult.Code.class); ArgumentCaptor groupDetailsCaptor = ArgumentCaptor.forClass(DataDownloadFileGroupStats.class); verify(mockLogger) .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture()); // Also clearing the file group version number becaused it ends up not being attached since // the pending group was removed. DataDownloadFileGroupStats expectedGroupDetails = createFileGroupDetails(fileGroup).clearFileCount().clearFileGroupVersionNumber().build(); assertThat(resultCodeCaptor.getAllValues()) .containsExactly(MddDownloadResult.Code.CUSTOM_FILEGROUP_VALIDATION_FAILED); assertThat(groupDetailsCaptor.getAllValues()).containsExactly(expectedGroupDetails); } @Test public void testDownloadFileGroup_failed() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setVariantId("test-variant") .setBuildId(10) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup); // Not all files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED)); // First file failed. Uri failingFileUri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), fileGroup.getFile(0).getFileId(), fileGroup.getFile(0).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadFails(keys[0], failingFileUri, DownloadResultCode.LOW_DISK_ERROR); // Second file succeeded. Uri succeedingFileUri = DirectoryUtil.getOnDeviceUri( context, keys[1].getAllowedReaders(), fileGroup.getFile(1).getFileId(), fileGroup.getFile(1).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadSucceeds(keys[1], succeedingFileUri); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()); ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException cause = (AggregateException) exception.getCause(); assertThat(cause).isNotNull(); ImmutableList failures = cause.getFailures(); assertThat(failures).hasSize(1); assertThat(failures.get(0)).isInstanceOf(DownloadException.class); assertThat(failures.get(0)).hasMessageThat().contains("LOW_DISK_ERROR"); // Verify that the pending group is still part of pending groups prefs. assertThat(readPendingFileGroup(testKey)).isNotNull(); // Verify that the pending group is not changed from pending to downloaded. assertThat(readDownloadedFileGroup(testKey)).isNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 10, /* variantId= */ "test-variant"); ArgumentCaptor resultCodeCaptor = ArgumentCaptor.forClass(MddDownloadResult.Code.class); ArgumentCaptor groupDetailsCaptor = ArgumentCaptor.forClass(DataDownloadFileGroupStats.class); verify(mockLogger) .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture()); DataDownloadFileGroupStats expectedGroupDetails = createFileGroupDetails(fileGroup).clearFileCount().build(); assertThat(resultCodeCaptor.getAllValues()) .containsExactly(MddDownloadResult.Code.LOW_DISK_ERROR); assertThat(groupDetailsCaptor.getAllValues()).containsExactly(expectedGroupDetails); } @Test public void testDownloadFileGroup_failedWithMultipleExceptions() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 3); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup); // Not all files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED)); // First file succeeded. Uri succeedingFileUri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), fileGroup.getFile(0).getFileId(), fileGroup.getFile(0).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadSucceeds(keys[0], succeedingFileUri); // Second file failed with download transform I/O error. Uri failingFileUri1 = DirectoryUtil.getOnDeviceUri( context, keys[1].getAllowedReaders(), fileGroup.getFile(1).getFileId(), fileGroup.getFile(1).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadFails(keys[1], failingFileUri1, DownloadResultCode.DOWNLOAD_TRANSFORM_IO_ERROR); // Third file failed with android downloader http error. Uri failingFileUri2 = DirectoryUtil.getOnDeviceUri( context, keys[2].getAllowedReaders(), fileGroup.getFile(2).getFileId(), fileGroup.getFile(2).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadFails(keys[2], failingFileUri2, DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()); // Ensure that all exceptions are aggregated. ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException cause = (AggregateException) exception.getCause(); assertThat(cause).isNotNull(); ImmutableList failures = cause.getFailures(); assertThat(failures).hasSize(2); assertThat(failures.get(0)).isInstanceOf(DownloadException.class); assertThat(failures.get(0)).hasMessageThat().contains("DOWNLOAD_TRANSFORM_IO_ERROR"); assertThat(failures.get(1)).isInstanceOf(DownloadException.class); assertThat(failures.get(1)).hasMessageThat().contains("ANDROID_DOWNLOADER_HTTP_ERROR"); // Verify that the pending group is still part of pending groups prefs. assertThat(readPendingFileGroup(testKey)).isNotNull(); // Verify that the pending group is not changed from pending to downloaded. assertThat(readDownloadedFileGroup(testKey)).isNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); ArgumentCaptor resultCodeCaptor = ArgumentCaptor.forClass(MddDownloadResult.Code.class); ArgumentCaptor groupDetailsCaptor = ArgumentCaptor.forClass(DataDownloadFileGroupStats.class); verify(mockLogger, times(2)) .logMddDownloadResult(resultCodeCaptor.capture(), groupDetailsCaptor.capture()); DataDownloadFileGroupStats expectedGroupDetails = createFileGroupDetails(fileGroup).clearFileCount().build(); assertThat(resultCodeCaptor.getAllValues()) .containsExactly( MddDownloadResult.Code.DOWNLOAD_TRANSFORM_IO_ERROR, MddDownloadResult.Code.ANDROID_DOWNLOADER_HTTP_ERROR); assertThat(groupDetailsCaptor.getAllValues()) .containsExactly(expectedGroupDetails, expectedGroupDetails); } @Test public void testDownloadFileGroup_failedWithUnknownError() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE)); // First file failed. Uri failingFileUri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), fileGroup.getFile(0).getFileId(), fileGroup.getFile(0).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); // The file status is set to DOWNLOAD_FAILED but the downloader returns an immediateVoidFuture. // An UNKNOWN_ERROR is logged. fileDownloadFails(keys[0], failingFileUri, /* failureCode= */ null); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()); ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); assertThat(exception).hasMessageThat().contains("UNKNOWN_ERROR"); // Verify that the pending group is still part of pending groups prefs. assertThat(readPendingFileGroup(testKey)).isNotNull(); // Verify that the pending group is not changed from pending to downloaded. assertThat(readDownloadedFileGroup(testKey)).isNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); } @Test public void testDownloadFileGroup_pending() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup); // Not all files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.SUBSCRIBED)); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), any(Uri.class), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .thenReturn(Futures.immediateVoidFuture()); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()); ExecutionException exception = assertThrows(ExecutionException.class, downloadFuture::get); assertThat(exception).hasMessageThat().contains("UNKNOWN_ERROR"); // Verify that the pending group is still part of pending groups prefs. assertThat(readPendingFileGroup(testKey)).isNotNull(); // Verify that the pending group is not changed from pending to downloaded. assertThat(readDownloadedFileGroup(testKey)).isNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.UNKNOWN_ERROR, createFileGroupDetails(fileGroup) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); } @Test public void testDownloadFileGroup_alreadyDownloaded() throws Exception { // Write 1 group to the downloaded shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writeDownloadedFileGroup(testKey, fileGroup); List originalKeys = fileGroupsMetadata.getAllGroupKeys().get(); ListenableFuture downloadFuture = fileGroupManager.downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()); assertThat(downloadFuture.get()).isEqualTo(fileGroup); // Verify that the downloaded group is still part of downloaded groups prefs. DataFileGroupInternal downloadedGroup = readDownloadedFileGroup(testKey); assertThat(downloadedGroup).isEqualTo(fileGroup); // Verify that no group metadata is written or removed. assertThat(originalKeys).isEqualTo(fileGroupsMetadata.getAllGroupKeys().get()); } @Test public void testDownloadFileGroup_nullDownloadCondition() throws Exception { DownloadConditions downloadConditions = DownloadConditions.newBuilder() .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI) .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE) .build(); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(downloadConditions) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED)); ArgumentCaptor downloadConditionsCaptor = ArgumentCaptor.forClass(DownloadConditions.class); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), any(Uri.class), any(String.class), anyInt(), downloadConditionsCaptor.capture(), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); return Futures.immediateVoidFuture(); } }); DataFileGroupInternal updatedFileGroup = fileGroup.toBuilder() .setBookkeeping( DataFileGroupBookkeeping.newBuilder() .setDownloadStartedCount(1) .setGroupDownloadStartedTimestampInMillis(1000L)) .build(); // Calling with DownloadConditions = null will use the config from server. assertThat( fileGroupManager .downloadFileGroup(testKey, null /*downloadConditions*/, noCustomValidation()) .get()) .isEqualTo(updatedFileGroup); assertThat(downloadConditionsCaptor.getValue()).isEqualTo(downloadConditions); } @Test public void testDownloadFileGroup_nonNullDownloadCondition() throws Exception { DownloadConditions downloadConditions = DownloadConditions.newBuilder() .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ONLY_ON_WIFI) .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE) .build(); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(downloadConditions) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED)); ArgumentCaptor downloadConditionsCaptor = ArgumentCaptor.forClass(DownloadConditions.class); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), any(Uri.class), any(String.class), anyInt(), downloadConditionsCaptor.capture(), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); return Futures.immediateVoidFuture(); } }); DownloadConditions downloadConditions2 = DownloadConditions.newBuilder() .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK) .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE) .build(); DataFileGroupInternal updatedFileGroup = fileGroup.toBuilder() .setBookkeeping( DataFileGroupBookkeeping.newBuilder() .setDownloadStartedCount(1) .setGroupDownloadStartedTimestampInMillis(1000L)) .build(); // downloadConditions2 will override the pendingGroup.downloadConditions assertThat( fileGroupManager .downloadFileGroup(testKey, downloadConditions2, noCustomValidation()) .get()) .isEqualTo(updatedFileGroup); assertThat(downloadConditionsCaptor.getValue()).isEqualTo(downloadConditions2); } @Test public void testDownloadFileGroup_notFoundGroup() throws Exception { // Mock FileGroupsMetadata to test failure scenario. resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager); // Can't find the group. ArgumentCaptor groupKeyCaptor = ArgumentCaptor.forClass(GroupKey.class); when(mockFileGroupsMetadata.read(groupKeyCaptor.capture())) .thenReturn(Futures.immediateFuture(null)); // Download not-found group will lead to failed future. ExecutionException exception = assertThrows( ExecutionException.class, () -> fileGroupManager .downloadFileGroup(testKey, null /*downloadConditions*/, noCustomValidation()) .get()); assertThat(exception).hasCauseThat().isInstanceOf(DownloadException.class); // Make sure that file group manager attempted to read both pending key and downloaded key. assertThat(groupKeyCaptor.getAllValues()) .containsAtLeast(getPendingKey(testKey), getDownloadedKey(testKey)); } @Test public void testDownloadFileGroup_downloadStartedTimestampAbsent() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); DataFileGroupBookkeeping bookkeeping = readDownloadedFileGroup(testKey).getBookkeeping(); assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue(); // Make sure that the download started timestamp is set to current time. assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis()) .isEqualTo(testClock.currentTimeMillis()); // Make sure that the download started count is accumulated. assertThat(bookkeeping.getDownloadStartedCount()).isEqualTo(1); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, fileGroup.getGroupName(), fileGroup.getFileGroupVersionNumber(), /* buildId= */ 0, /* variantId= */ ""); } @Test public void testDownloadFileGroup_downloadStartedTimestampPresent() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .setBookkeeping( DataFileGroupBookkeeping.newBuilder() .setGroupDownloadStartedTimestampInMillis(123456) .setDownloadStartedCount(2)) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); DataFileGroupBookkeeping bookkeeping = readDownloadedFileGroup(testKey).getBookkeeping(); assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue(); // Make sure that the download started timestamp is not changed. assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis()).isEqualTo(123456); // Make sure that the download started count is accumulated. assertThat(bookkeeping.getDownloadStartedCount()).isEqualTo(3); verify(mockLogger, never()) .logEventSampled( eq(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED), any(String.class), anyInt(), anyLong(), any(String.class)); } @Test public void testDownloadFileGroup_updateBookkeepingOnDownloadFailed() throws Exception { // Mock FileGroupsMetadata to test failure scenario. resetFileGroupManager(mockFileGroupsMetadata, sharedFileManager); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .build(); GroupKey pendingKey = testKey.toBuilder().setDownloaded(false).build(); when(mockFileGroupsMetadata.read(pendingKey)).thenReturn(Futures.immediateFuture(fileGroup)); // All files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); ArgumentCaptor fileGroupCaptor = ArgumentCaptor.forClass(DataFileGroupInternal.class); when(mockFileGroupsMetadata.write(eq(pendingKey), fileGroupCaptor.capture())) .thenReturn(Futures.immediateFuture(false)); ExecutionException executionException = assertThrows( ExecutionException.class, () -> fileGroupManager .downloadFileGroup( testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get()); assertThat(executionException).hasCauseThat().isInstanceOf(DownloadException.class); DownloadException downloadException = (DownloadException) executionException.getCause(); assertThat(downloadException).hasCauseThat().isInstanceOf(IOException.class); assertThat(downloadException.getDownloadResultCode()) .isEqualTo(DownloadResultCode.UNABLE_TO_UPDATE_GROUP_METADATA_ERROR); DataFileGroupBookkeeping bookkeeping = fileGroupCaptor.getValue().getBookkeeping(); assertThat(bookkeeping.hasGroupDownloadStartedTimestampInMillis()).isTrue(); assertThat(bookkeeping.getGroupDownloadStartedTimestampInMillis()) .isEqualTo(testClock.currentTimeMillis()); verify(mockLogger, never()) .logEventSampled( eq(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED), any(String.class), anyInt(), anyLong(), any(String.class)); verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED); } @Test public void testDownloadToBeSharedPendingGroup_success_lowSdk_notShared() throws Exception { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.R - 1); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = createDataFileGroup( TEST_GROUP, /* fileCount= */ 0, /* downloadAttemptCount= */ 3, /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1)) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.SUCCESS, createFileGroupDetails(fileGroup) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); verify(mockLogger) .logMddDownloadLatency( createFileGroupDetails(fileGroup).build(), createMddDownloadLatency( /* downloadAttemptCount= */ 4, /* downloadLatencyMs= */ 0L, /* totalLatencyMs= */ 500L)); // exists only called once in tryToShareBeforeDownload verify(mockBackend, never()).exists(any()); // openForWrite is called in tryToShareBeforeDownload for copying the file and acquiring the // lease. verify(mockBackend, never()).openForWrite(any()); } @Test public void testDownloadFileGroup_success_oneFileAndroidSharedAndDownloaded() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setVariantId("test-variant") .setBuildId(10) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE), /* androidShared */ ImmutableList.of(true, false)); SharedFile file0 = sharedFileManager.getSharedFile(keys[0]).get(); SharedFile file1 = sharedFileManager.getSharedFile(keys[1]).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file0.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, file0.getAndroidSharingChecksum(), 0); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 10, /* variantId= */ "test-variant"); verify(mockBackend, never()).exists(blobUri); verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend, never()).openForWrite(leaseUri); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(file0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(file1); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; Void expectedLog = null; assertThat(mddAndroidSharingLog).isEqualTo(expectedLog); } @Test public void testDownloadFileGroup_pending_oneBlobExistsBeforeDownload() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1)) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.SUBSCRIBED)); // File that can be shared DataFile file = fileGroup.getFile(0); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); // First file's download succeeds Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), file.getFileId(), keys[0].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); fileDownloadSucceeds(keys[0], onDeviceuri); // Second file's download succeeds onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[1].getAllowedReaders(), fileGroup.getFile(1).getFileId(), keys[1].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); fileDownloadSucceeds(keys[1], onDeviceuri); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that the pending group is not part of pending groups prefs. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that the downloaded group is still part of downloaded groups prefs. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockBackend).exists(blobUri); // openForWrite is called only once in tryToShareBeforeDownload for acquiring the lease. verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile expectedSharedFile0 = SharedFile.newBuilder() .setFileName("android_shared_sha256_1230") .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setAndroidShared(true) .setAndroidSharingChecksum("sha256_1230") .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); SharedFile expectedSharedFile1 = SharedFile.newBuilder() .setFileName(fileGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; Void expectedLog = null; assertThat(mddAndroidSharingLog).isEqualTo(expectedLog); } @Test public void testDownloadFileGroup_pending_oneBlobExistsAfterDownload() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); final DataFileGroupInternal fileGroup = tmpFileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1)) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE)); // File that can be shared DataFile file = fileGroup.getFile(0); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file isn't available in the blob storage when tryToShareBeforeDownload is called when(mockBackend.exists(blobUri)).thenReturn(false); simulateDownload(file, file.getFileId()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), file.getFileId(), keys[0].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), eq(onDeviceuri), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { // The file now exists in the shared storage when(mockBackend.exists(blobUri)).thenReturn(true); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); return Futures.immediateVoidFuture(); } }); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); // exists called once in tryToShareBeforeDownload and once in tryToShareAfterDownload verify(mockBackend, times(2)).exists(blobUri); // openForWrite is called only once in tryToShareAfterDownload for acquiring the lease. verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile expectedSharedFile0 = SharedFile.newBuilder() .setFileName("android_shared_sha256_1230") .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setAndroidShared(true) .setAndroidSharingChecksum("sha256_1230") .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); SharedFile expectedSharedFile1 = SharedFile.newBuilder() .setFileName(fileGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1); // Local copy has not been deleted. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; Void expectedLog = null; assertThat(mddAndroidSharingLog).isEqualTo(expectedLog); } @Test public void testDownloadFileGroup_success_oneFileCanBeCopiedBeforeDownload() throws Exception { File tempFile = folder.newFile("blobFile"); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); fileGroup = fileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1)) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); // All files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); DataFile file = fileGroup.getFile(0); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, file.getAndroidSharingChecksum(), 0); // The file isn't available yet in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); // File that can be copied to the blob storage when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile)); File onDeviceFile = simulateDownload(file, fileGroup.getFile(0).getFileId()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), fileGroup.getFile(0).getFileId(), keys[0].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); // exists only called once in tryToShareBeforeDownload verify(mockBackend).exists(blobUri); // openForWrite is called in tryToShareBeforeDownload for copying the file and acquiring the // lease. verify(mockBackend).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile expectedSharedFile0 = SharedFile.newBuilder() .setFileName("android_shared_sha256_1230") .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setAndroidShared(true) .setAndroidSharingChecksum("sha256_1230") .setMaxExpirationDateSecs(0) .build(); SharedFile expectedSharedFile1 = SharedFile.newBuilder() .setFileName(fileGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; Void expectedLog = null; assertThat(mddAndroidSharingLog).isEqualTo(expectedLog); } @Test public void testDownloadFileGroup_oneFileCanBeCopiedAfterDownload() throws Exception { File tempFile = folder.newFile("blobFile"); // Write 1 group to the pending shared prefs. DataFileGroupInternal tmpFfileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); final DataFileGroupInternal fileGroup = tmpFfileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createSharedDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createDataFile(TEST_GROUP, 1)) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED, FileStatus.DOWNLOAD_COMPLETE)); // File that can be copied to the blob storage DataFile file = fileGroup.getFile(0); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri(context, file.getAndroidSharingChecksum(), 0); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); simulateDownload(file, file.getFileId()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), file.getFileId(), keys[0].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), eq(onDeviceuri), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { // The file will be copied in tryToShareAfterDownload when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile)); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); return Futures.immediateVoidFuture(); } }); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); // exists only called once in tryToShareBeforeDownload, once in tryToShareAfterDownload verify(mockBackend, times(2)).exists(blobUri); // File copied once in tryToShareAfterDownload verify(mockBackend).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile expectedSharedFile0 = SharedFile.newBuilder() .setFileName("android_shared_sha256_1230") .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setAndroidShared(true) .setAndroidSharingChecksum("sha256_1230") .setMaxExpirationDateSecs(0) .build(); SharedFile expectedSharedFile1 = SharedFile.newBuilder() .setFileName(fileGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1); // Local copy has not been deleted. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; Void expectedLog = null; assertThat(mddAndroidSharingLog).isEqualTo(expectedLog); } @Test public void testDownloadFileGroup_nonToBeSharedFile_neverShared() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); final DataFileGroupInternal fileGroup = tmpFileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); writeSharedFiles(sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.SUBSCRIBED)); DataFile file = fileGroup.getFile(0); File onDeviceFile = simulateDownload(file, file.getFileId()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, keys[0].getAllowedReaders(), file.getFileId(), keys[0].getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileDownloadSucceeds(keys[0], onDeviceuri); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); SharedFile expectedSharedFile = SharedFile.newBuilder() .setFileName(file.getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); } @Test public void testDownloadFileGroup_androidSharingFails() throws Exception { // Write 1 group to the pending shared prefs. DataFileGroupInternal tmpFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0); ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); final DataFileGroupInternal fileGroup = tmpFileGroup.toBuilder() .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .addFile(0, MddTestUtil.createDataFile(TEST_GROUP, 0)) .addFile(1, MddTestUtil.createSharedDataFile(TEST_GROUP, 1)) .build(); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); // Second file fails with file storage I/O exception when called from tryToShareBeforeDownload // and tryToShareAfterDownload. DataFile file = fileGroup.getFile(1); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); when(mockBackend.exists(blobUri)).thenThrow(new IOException()); // Any error during sharing doesn't stop the download: the file will be stored locally. fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isNull(); // Verify that downloaded key is written into metadata if download is complete. assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockBackend, times(2)).exists(blobUri); verify(mockBackend, never()).openForWrite(any()); SharedFile expectedSharedFile0 = SharedFile.newBuilder() .setFileName(fileGroup.getFile(0).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); SharedFile expectedSharedFile1 = expectedSharedFile0.toBuilder().setFileName(fileGroup.getFile(1).getFileId()).build(); assertThat(sharedFileManager.getSharedFile(keys[0]).get()).isEqualTo(expectedSharedFile0); assertThat(sharedFileManager.getSharedFile(keys[1]).get()).isEqualTo(expectedSharedFile1); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.SUCCESS, DataDownloadFileGroupStats.newBuilder() .setFileGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .setFileGroupVersionNumber(0) .setBuildId(0) .setVariantId("") .build()); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger, times(2)) .logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLogBeforeAndAfterDownload = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly( mddAndroidSharingLogBeforeAndAfterDownload, mddAndroidSharingLogBeforeAndAfterDownload); } @Test public void testDownloadFileGroup_skipsSideloadedFiles() throws Exception { // Create sideloaded group with normal file DataFileGroupInternal sideloadedGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .addFile( DataFile.newBuilder() .setFileId("sideloaded_file") .setUrlToDownload("file:/test") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .addFile( DataFile.newBuilder() .setFileId("normal_file") .setUrlToDownload("https://url.to.download") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .build(); NewFileKey normalFileKey = SharedFilesMetadata.createKeyFromDataFile( sideloadedGroup.getFile(1), sideloadedGroup.getAllowedReadersEnum()); // Write group as pending since we are waiting on normal file writePendingFileGroup(testKey, sideloadedGroup); SharedFile normalSharedFile = SharedFile.newBuilder() .setFileName(sideloadedGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.SUBSCRIBED) .build(); sharedFilesMetadata.write(normalFileKey, normalSharedFile).get(); // Mock that download of normal file succeeds Uri normalFileUri = DirectoryUtil.getOnDeviceUri( context, normalFileKey.getAllowedReaders(), sideloadedGroup.getFile(1).getFileId(), sideloadedGroup.getFile(1).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadSucceeds(normalFileKey, normalFileUri); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); assertThat(readPendingFileGroup(testKey)).isNull(); assertThat(readDownloadedFileGroup(testKey)).isNotNull(); verify(mockDownloader) .startDownloading( eq(sideloadedGroup.getFile(1).getChecksum()), eq(testKey), anyInt(), anyLong(), any(String.class), eq(normalFileUri), eq(sideloadedGroup.getFile(1).getUrlToDownload()), anyInt(), any(), any(), anyInt(), anyList()); } @Test public void testDownloadFileGroup_whenMultipleVariantsExist_downloadsSpecifiedVariant() throws Exception { GroupKey defaultGroupKey = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .build(); GroupKey enGroupKey = defaultGroupKey.toBuilder().setVariantId("en").build(); DataFileGroupInternal defaultFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1); // Create EN with custom file ids so it doesn't overlap with the default file group. DataFileGroupInternal enFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .addFile(MddTestUtil.createDataFile("en", 0)) .addFile(MddTestUtil.createDataFile("en", 1)) .build(); writePendingFileGroup(getPendingKey(defaultGroupKey), defaultFileGroup); writePendingFileGroup(getPendingKey(enGroupKey), enFileGroup); writeSharedFiles( sharedFilesMetadata, defaultFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup( defaultGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that correct group was downloaded assertThat(readPendingFileGroup(defaultGroupKey)).isNull(); assertThat(readDownloadedFileGroup(defaultGroupKey)).isNotNull(); assertThat(readPendingFileGroup(enGroupKey)).isNotNull(); assertThat(readDownloadedFileGroup(enGroupKey)).isNull(); // Attempt to download en group and check that it is now downloaded writeSharedFiles( sharedFilesMetadata, enFileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup( enGroupKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); // Verify that correct group was downloaded assertThat(readPendingFileGroup(defaultGroupKey)).isNull(); assertThat(readDownloadedFileGroup(defaultGroupKey)).isNotNull(); assertThat(readPendingFileGroup(enGroupKey)).isNull(); assertThat(readDownloadedFileGroup(enGroupKey)).isNotNull(); } @Test public void testDownloadAllPendingGroups_onWifi() throws Exception { // Write 3 groups to the pending shared prefs. // MDD successfully downloaded filegroup1, partially downloaded filegroup2 and failed to // download filegroup3. DataFileGroupInternal fileGroup1 = createDataFileGroup( TEST_GROUP, /* fileCount= */ 2, /* downloadAttemptCount= */ 7, /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L); fileGroup1 = fileGroup1.toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup1); // All files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); fileGroup2 = fileGroup2.toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey2, fileGroup2); // Not all files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS)); GroupKey expectedKey2 = testKey2.toBuilder().setDownloaded(false).build(); // The file status isn't changed to DOWNLOAD_COMPLETE, it remains DOWNLOAD_IN_PROGRESS. // An UNKNOWN_ERROR is logged. when(mockDownloader.getInProgressFuture(any(String.class), any(Uri.class))) .thenReturn(Futures.immediateFuture(Optional.of(Futures.immediateVoidFuture()))); DataFileGroupInternal tmpFileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2); final DataFileGroupInternal fileGroup3 = tmpFileGroup3.toBuilder() .setDownloadConditions( DownloadConditions.newBuilder().setDownloadFirstOnWifiPeriodSecs(1000000)) .build(); writePendingFileGroup(testKey3, fileGroup3); // Not all files are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup3, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED)); GroupKey expectedKey3 = testKey3.toBuilder().setDownloaded(false).build(); // One file fails, status is DOWNLOAD_FAILED but the downloader returns an // immediateVoidFuture. An UNKNOWN_ERROR is logged. when(mockDownloader.startDownloading( any(String.class), eq(expectedKey3), anyInt(), anyLong(), any(String.class), any(Uri.class), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { writeSharedFiles( sharedFilesMetadata, fileGroup3, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED)); return Futures.immediateVoidFuture(); } }); fileGroupManager.scheduleAllPendingGroupsForDownload(true, noCustomValidation()).get(); verify(mockLogger) .logMddDownloadLatency( createFileGroupDetails(fileGroup1).build(), createMddDownloadLatency( /* downloadAttemptCount= */ 8, /* downloadLatencyMs= */ 0L, /* totalLatencyMs= */ 500L)); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_3, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); // Make sure that the successful download of fileGroup1, the failed downloads of fileGroup2 and // fileGroup3 are all logged to clearcut. verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.SUCCESS, createFileGroupDetails(fileGroup1) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.UNKNOWN_ERROR, createFileGroupDetails(fileGroup2) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.UNKNOWN_ERROR, createFileGroupDetails(fileGroup3) .setOwnerPackage(context.getPackageName()) .clearFileCount() .build()); } @Test public void testDownloadAllPendingGroups_withoutWifi() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); fileGroup1 = fileGroup1.toBuilder() .setDownloadConditions( DownloadConditions.newBuilder() .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)) .build(); writePendingFileGroup(testKey, fileGroup1); writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 3); writePendingFileGroup(testKey2, fileGroup2); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of( FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS, FileStatus.DOWNLOAD_IN_PROGRESS)); fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get(); // Only the files in the first group will be downloaded. verify(mockDownloader, times(2)).getInProgressFuture(any(String.class), any(Uri.class)); verifyNoMoreInteractions(mockDownloader); } @Test public void testDownloadAllPendingGroups_wifiFirst_without_Wifi() throws Exception { DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions( DownloadConditions.newBuilder() .setDeviceNetworkPolicy( DeviceNetworkPolicy.DOWNLOAD_FIRST_ON_WIFI_THEN_ON_ANY_NETWORK) .setDownloadFirstOnWifiPeriodSecs(10)) .build(); testClock.set(1000); { // Check that pending groups contain the added file group. assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); verifyAddGroupForDownloadWritesMetadata(testKey, dataFileGroup, 1000); } { // Set time so that it has not passed the wifi only period. testClock.set(2000); fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get(); } { // Set time so that it has passed the wifi only period. testClock.set(2000 + 10 * 1000); ArgumentCaptor downloadConditionCaptor = ArgumentCaptor.forClass(DownloadConditions.class); when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), any(Uri.class), any(String.class), anyInt(), downloadConditionCaptor.capture(), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .thenReturn(Futures.immediateVoidFuture()); fileGroupManager.scheduleAllPendingGroupsForDownload(false, noCustomValidation()).get(); // verify that the group's DeviceNetworkPolicy changes to // DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK assertThat(downloadConditionCaptor.getValue().getDeviceNetworkPolicy()) .isEqualTo(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK); } } @Test public void testDownloadAllPendingGroups_startDownloadFails() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); writePendingFileGroup(testKey, fileGroup1); writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.SUBSCRIBED)); NewFileKey[] keys1 = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 1); writePendingFileGroup(testKey2, fileGroup2); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); // Make the download call fail for one of the files in first group. Uri failingFileUri = DirectoryUtil.getOnDeviceUri( context, keys1[1].getAllowedReaders(), fileGroup1.getFile(1).getFileId(), fileGroup1.getFile(1).getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); fileDownloadFails(keys1[1], failingFileUri, DownloadResultCode.LOW_DISK_ERROR); fileGroupManager.scheduleAllPendingGroupsForDownload(true, noCustomValidation()).get(); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.LOW_DISK_ERROR, createFileGroupDetails(fileGroup1) .clearFileCount() .setOwnerPackage(context.getPackageName()) .build()); verify(mockLogger) .logMddDownloadResult( MddDownloadResult.Code.SUCCESS, createFileGroupDetails(fileGroup2) .clearFileCount() .setOwnerPackage(context.getPackageName()) .build()); } // case 1: the file is already shared in the blob storage. @Test public void tryToShareBeforeDownload_alreadyShared() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata as already downloaded and shared SharedFile existingDownloadedSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("") .setAndroidShared(true) .setAndroidSharingChecksum(file.getAndroidSharingChecksum()) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingDownloadedSharedFile).get(); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); // openForWrite isn't called to update the lease because the current fileGroup's expiration date // is < maxExpirationDate. verify(mockBackend, never()).openForWrite(any()); assertThat(sharedFileManager.getSharedFile(newFileKey).get()) .isEqualTo(existingDownloadedSharedFile); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } // case 2a: the to-be-shared file is available in the blob storage. @Test public void tryToShareBeforeDownload_toBeSharedFile_blobExists() throws Exception { // Create a file group with expiration date smaller than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS - 1) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata as download pending and non shared SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); // openForWrite is called only once for acquiring the lease. verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile retains the longest expiration date after the download. assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS); assertThat(sharedFile.getAndroidShared()).isTrue(); assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } // case 3: the to-be-shared file is available in the local storage. @Test public void tryToShareBeforeDownload_toBeSharedFile_canBeCopied() throws Exception { File tempFile = folder.newFile("blobFile"); // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata as downloaded and non shared SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1); // The file isn't available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile)); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); // openForWrite is called once for writing the blob, once for acquiring the lease. verify(mockBackend).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. assertThat(sharedFile.getMaxExpirationDateSecs()) .isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS + 1); assertThat(sharedFile.getAndroidShared()).isTrue(); assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri); // The local copy will be deleted in daily maintance assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } // The file can't be shared and isn't available locally. @Test public void tryToShareBeforeDownload_toBeSharedFile_cannotBeShared_neverDownloaded() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS) .setFileName("fileName") .setAndroidShared(false) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); // The file isn't available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); // We never acquire the lease nor update the max expiration date. verify(mockBackend).exists(blobUri); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); verifyNoInteractions(mockLogger); } // case 4: the non-to-be-shared file can't be shared and is available in the local storage. @Test public void tryToShareBeforeDownload_nonToBeSharedFile_alreadyDownloaded_cannotBeShared() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // non-to-be-shared file with ChecksumType SHA1 DataFile file = MddTestUtil.createDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata downloaded and non shared SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); // We never acquire the lease since the file can't be shared. verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); verify(mockSharedFileManager, never()) .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong()); verifyNoInteractions(mockLogger); } @Test public void tryToShareBeforeDownload_blobUriNotSupported() throws Exception { // FileStorage without BlobStoreBackend fileStorage = new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build())); fileGroupManager = new FileGroupManager( context, mockLogger, mockSilentFeedback, fileGroupsMetadata, sharedFileManager, testClock, Optional.of(mockAccountSource), SEQUENTIAL_CONTROL_EXECUTOR, Optional.absent(), fileStorage, downloadStageManager, flags); DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata as download completed and non shared SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareBeforeDownload_setAndroidSharedDownloadedFileEntryReturnsFalse() throws Exception { // Mock SharedFileManager to test failure scenario. resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); when(mockSharedFileManager.getSharedFile(newFileKey)) .thenReturn(Futures.immediateFuture(existingSharedFile)); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); // Last operation fails when(mockSharedFileManager.setAndroidSharedDownloadedFileEntry( newFileKey, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS)) .thenReturn(Futures.immediateFuture(false)); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); // openForWrite is called only once for acquiring the lease. verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareBeforeDownload_blobExistsThrowsIOException() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); when(mockBackend.exists(blobUri)).thenReturn(true); when(mockBackend.openForWrite(leaseUri)).thenThrow(new IOException()); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareBeforeDownload_fileStorageThrowsLimitExceededException() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_IN_PROGRESS) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); // Writing the lease throws an exception when(mockBackend.openForWrite(leaseUri)).thenThrow(new LimitExceededException()); fileGroupManager.tryToShareBeforeDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Since there was an exception, the existing shared file didn't update the expiration date. assertThat(sharedFile).isEqualTo(existingSharedFile); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_alreadyShared_sameFileGroup() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(true) .setAndroidSharingChecksum(file.getAndroidSharingChecksum()) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); verifyNoInteractions(mockLogger); } @Test public void tryToShareAfterDownload_alreadyShared_differentFileGroup() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(true) .setAndroidSharingChecksum(file.getAndroidSharingChecksum()) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS - 1) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); // openForWrite is called only once for acquiring the lease. verify(mockBackend, never()).exists(any()); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS); assertThat(sharedFile.getAndroidShared()).isTrue(); assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_toBeSharedFile_blobExists() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend).exists(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS); assertThat(sharedFile.getAndroidShared()).isTrue(); assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri); // Local copy has not been deleted. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_toBeSharedFile_canBeCopied() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. File tempFile = folder.newFile("blobFile"); DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file isn't available yet in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); when(mockBackend.openForWrite(blobUri)).thenReturn(new FileOutputStream(tempFile)); simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); // openForWrite is called once for writing the blob, once for acquiring the lease. verify(mockBackend).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. assertThat(sharedFile.getMaxExpirationDateSecs()).isEqualTo(FILE_GROUP_EXPIRATION_DATE_SECS); assertThat(sharedFile.getAndroidShared()).isTrue(); assertThat(sharedFileManager.getOnDeviceUri(newFileKey).get()).isEqualTo(blobUri); // Local copy has not been deleted. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_nonToBeSharedFile_neverShared() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. SharedFile expectedSharedFile = existingSharedFile.toBuilder() .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); assertThat(sharedFile).isEqualTo(expectedSharedFile); // Local copy still available. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); verifyNoInteractions(mockLogger); } @Test public void tryToShareAfterDownload_toBeSharedFile_neverShared() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); // This should never happened in a real scenario. file = file.toBuilder().setAndroidSharingChecksum("").build(); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Verify that the SharedFile has updated its expiration date after the download. SharedFile expectedSharedFile = existingSharedFile.toBuilder() .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); assertThat(sharedFile).isEqualTo(expectedSharedFile); // Local copy still available. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_blobUriNotSupported() throws Exception { // FileStorage without BlobStoreBackend fileStorage = new SynchronousFileStorage(Arrays.asList(AndroidFileBackend.builder(context).build())); fileGroupManager = new FileGroupManager( context, mockLogger, mockSilentFeedback, fileGroupsMetadata, sharedFileManager, testClock, Optional.of(mockAccountSource), SEQUENTIAL_CONTROL_EXECUTOR, Optional.absent(), fileStorage, downloadStageManager, flags); DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 0).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); // Set the file metadata as download completed and non shared SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).openForWrite(any()); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_nonExistentFile() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); ListenableFuture tryToShareFuture = fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey); ExecutionException exception = assertThrows(ExecutionException.class, tryToShareFuture::get); assertThat(exception).hasCauseThat().isInstanceOf(SharedFileMissingException.class); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); verify(mockSharedFileManager, never()) .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong()); verify(mockSharedFileManager, never()).updateMaxExpirationDateSecs(newFileKey, 0); } @Test public void tryToShareAfterDownload_updateMaxExpirationDateSecsReturnsFalse() throws Exception { // Mock SharedFileManager to test failure scenario. resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder() .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); DataFile file = MddTestUtil.createDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .build(); when(mockSharedFileManager.getSharedFile(newFileKey)) .thenReturn(Futures.immediateFuture(existingSharedFile)); when(mockSharedFileManager.updateMaxExpirationDateSecs( newFileKey, FILE_GROUP_EXPIRATION_DATE_SECS)) .thenReturn(Futures.immediateFuture(false)); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).exists(any()); verify(mockBackend, never()).openForWrite(any()); verify(mockSharedFileManager, never()) .setAndroidSharedDownloadedFileEntry(any(), any(), anyLong()); verify(mockSharedFileManager) .updateMaxExpirationDateSecs(newFileKey, FILE_GROUP_EXPIRATION_DATE_SECS); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_setAndroidSharedDownloadedFileEntryReturnsFalse() throws Exception { // Mock SharedFileManager to test failure scenario. resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); when(mockSharedFileManager.getSharedFile(newFileKey)) .thenReturn(Futures.immediateFuture(existingSharedFile)); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); // Last operation fails when(mockSharedFileManager.setAndroidSharedDownloadedFileEntry( newFileKey, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS)) .thenReturn(Futures.immediateFuture(false)); when(mockSharedFileManager.updateMaxExpirationDateSecs(newFileKey, 0)) .thenReturn(Futures.immediateFuture(true)); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend).exists(blobUri); // openForWrite is called only once for acquiring the lease. verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); verify(mockSharedFileManager).updateMaxExpirationDateSecs(newFileKey, 0); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_copyBlobThrowsIOException() throws Exception { DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS); // The file isn't available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(false); // Copying the blob throws an exception when(mockBackend.openForWrite(blobUri)).thenThrow(new IOException()); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); assertThat(sharedFile).isEqualTo(existingSharedFile); // Local copy still available. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void tryToShareAfterDownload_fileStorageThrowsLimitExceededException() throws Exception { // Create a file group with expiration date bigger than the expiration date of the existing // SharedFile. DataFileGroupInternal fileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1) .build(); // Create a to-be-shared file DataFile file = MddTestUtil.createSharedDataFile("fileId", 0); NewFileKey newFileKey = SharedFilesMetadata.createKeyFromDataFile(file, AllowedReaders.ALL_GOOGLE_APPS); SharedFile existingSharedFile = SharedFile.newBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .setFileName("fileName") .setAndroidShared(false) .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS) .build(); sharedFilesMetadata.write(newFileKey, existingSharedFile).get(); Uri blobUri = DirectoryUtil.getBlobUri(context, file.getAndroidSharingChecksum()); Uri leaseUri = DirectoryUtil.getBlobStoreLeaseUri( context, file.getAndroidSharingChecksum(), FILE_GROUP_EXPIRATION_DATE_SECS + 1); // The file is available in the blob storage when(mockBackend.exists(blobUri)).thenReturn(true); File onDeviceFile = simulateDownload(file, existingSharedFile.getFileName()); Uri onDeviceuri = DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), existingSharedFile.getFileName(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), false); assertThat(fileStorage.exists(onDeviceuri)).isTrue(); // Writing the lease throws an exception when(mockBackend.openForWrite(leaseUri)).thenThrow(new LimitExceededException()); fileGroupManager.tryToShareAfterDownload(fileGroup, file, newFileKey).get(); verify(mockBackend, never()).openForWrite(blobUri); verify(mockBackend).openForWrite(leaseUri); SharedFile sharedFile = sharedFileManager.getSharedFile(newFileKey).get(); // Even if there was an exception, the SharedFile has updated its expiration date after the // download. SharedFile expectedSharedFile = existingSharedFile.toBuilder() .setMaxExpirationDateSecs(FILE_GROUP_EXPIRATION_DATE_SECS + 1) .build(); assertThat(sharedFile).isEqualTo(expectedSharedFile); // Local copy still available. assertThat(fileStorage.exists(onDeviceuri)).isTrue(); onDeviceFile.delete(); ArgumentCaptor mddAndroidSharingLogArgumentCaptor = ArgumentCaptor.forClass(Void.class); verify(mockLogger).logMddAndroidSharingLog(mddAndroidSharingLogArgumentCaptor.capture()); Void mddAndroidSharingLog = null; assertThat(mddAndroidSharingLogArgumentCaptor.getAllValues()) .containsExactly(mddAndroidSharingLog); verifyNoMoreInteractions(mockLogger); } @Test public void testVerifyPendingGroupDownloaded() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(testKey2, fileGroup2); // Make the verify download call fail for one file in the first group. writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); testClock.set(/* millis */ 1000); assertThat( fileGroupManager .verifyGroupDownloaded( testKey, fileGroup1, /* removePendingVersion= */ true, noCustomValidation(), DownloadStateLogger.forDownload(mockLogger)) .get()) .isEqualTo(GroupDownloadStatus.PENDING); assertThat( fileGroupManager .verifyGroupDownloaded( testKey2, fileGroup2, /* removePendingVersion= */ true, noCustomValidation(), DownloadStateLogger.forDownload(mockLogger)) .get()) .isEqualTo(GroupDownloadStatus.DOWNLOADED); // Verify that the pending group is still part of pending groups prefs. DataFileGroupInternal pendingGroup1 = readPendingFileGroup(testKey); assertThat(pendingGroup1).isEqualTo(fileGroup1); // Verify that the pending group is not written into metadata. assertThat(readDownloadedFileGroup(testKey)).isNull(); fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000); // Verify that the completely downloaded group is written into metadata. DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2); assertThat(downloadedGroup2).isEqualTo(fileGroup2); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); } @Test public void testVerifyAllPendingGroupsDownloaded() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(testKey2, fileGroup2); // Make the verify download call fail for one file in the first group. writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_IN_PROGRESS)); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); testClock.set(/* millis */ 1000); fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get(); // Verify that the pending group is still part of pending groups prefs. DataFileGroupInternal pendingGroup1 = readPendingFileGroup(testKey); MddTestUtil.assertMessageEquals(fileGroup1, pendingGroup1); // Verify that the pending group is not written into metadata. assertThat(readDownloadedFileGroup(testKey)).isNull(); fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000); // Verify that the completely downloaded group is written into metadata. DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2); assertThat(downloadedGroup2).isEqualTo(fileGroup2); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); } @Test public void testVerifyAllPendingGroupsDownloaded_existingDownloadedGroup() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(testKey2, fileGroup2); // Also write 2 groups to the downloaded shared prefs. // fileGroup3 is the downloaded version if fileGroup1. DataFileGroupInternal fileGroup3 = MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP, 1); writeDownloadedFileGroup(testKey, fileGroup3); DataFileGroupInternal fileGroup4 = MddTestUtil.createDownloadedDataFileGroupInternal(TEST_GROUP_3, 2); writeDownloadedFileGroup(testKey3, fileGroup4); // All file are downloaded. writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, fileGroup3, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE)); writeSharedFiles( sharedFilesMetadata, fileGroup4, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); testClock.set(/* millis */ 1000); fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get(); // Verify that pending key is removed if the group is downloaded. assertThat(readPendingFileGroup(testKey)).isNull(); assertThat(readPendingFileGroup(testKey2)).isNull(); assertThat(readPendingFileGroup(testKey3)).isNull(); fileGroup1 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup1, 1000); fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000); // Verify that pending group is marked as downloaded group. DataFileGroupInternal downloadedGroup1 = readDownloadedFileGroup(testKey); assertThat(downloadedGroup1).isEqualTo(fileGroup1); DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2); assertThat(downloadedGroup2).isEqualTo(fileGroup2); DataFileGroupInternal downloadedGroup4 = readDownloadedFileGroup(testKey3); assertThat(downloadedGroup4).isEqualTo(fileGroup4); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); // fileGroup3 should have been scheduled for deletion. fileGroup3 = fileGroup3.toBuilder() .setBookkeeping(DataFileGroupBookkeeping.newBuilder().setStaleExpirationDate(1).build()) .build(); assertThat(fileGroupsMetadata.getAllStaleGroups().get()).containsExactly(fileGroup3); } @Test public void testGroupDownloadFailed() throws Exception { // Write 2 groups to the pending shared prefs. DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(testKey2, fileGroup2); // Make the second file of the first group fail. writeSharedFiles( sharedFilesMetadata, fileGroup1, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_FAILED)); writeSharedFiles( sharedFilesMetadata, fileGroup2, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager.verifyAllPendingGroupsDownloaded(noCustomValidation()).get(); // Verify that pending key is removed if download is complete. assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1); assertThat(readPendingFileGroup(testKey2)).isNull(); // Verify that downloaded key is written into metadata if download is complete. fileGroup2 = FileGroupUtil.setDownloadedTimestampInMillis(fileGroup2, 1000); DataFileGroupInternal downloadedGroup2 = readDownloadedFileGroup(testKey2); assertThat(downloadedGroup2).isEqualTo(fileGroup2); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); } @Test public void testDeleteUninstalledAppGroups_noUninstalledApps() throws Exception { PackageManager packageManager = context.getPackageManager(); final PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = context.getPackageName(); packageInfo.lastUpdateTime = System.currentTimeMillis(); Shadows.shadowOf(packageManager).addPackage(packageInfo); DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(testKey2, fileGroup2); fileGroupManager.deleteUninstalledAppGroups().get(); assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1); assertThat(readPendingFileGroup(testKey2)).isEqualTo(fileGroup2); } @Test public void testDeleteUninstalledAppGroups_uninstalledApp() throws Exception { PackageManager packageManager = context.getPackageManager(); final PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = context.getPackageName(); packageInfo.lastUpdateTime = System.currentTimeMillis(); Shadows.shadowOf(packageManager).addPackage(packageInfo); DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writePendingFileGroup(testKey, fileGroup1); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); GroupKey uninstalledAppKey = GroupKey.newBuilder().setGroupName(TEST_GROUP_2).setOwnerPackage("uninstalled.app").build(); writeDownloadedFileGroup(uninstalledAppKey, fileGroup2); assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1); assertThat(readDownloadedFileGroup(uninstalledAppKey)).isEqualTo(fileGroup2); fileGroupManager.deleteUninstalledAppGroups().get(); assertThat(readPendingFileGroup(testKey)).isEqualTo(fileGroup1); assertThat(readDownloadedFileGroup(uninstalledAppKey)).isNull(); } @Test public void testDeleteRemovedAccountGroups_noRemovedAccounts() throws Exception { Account account1 = new Account("name1", "type1"); Account account2 = new Account("name2", "type2"); when(mockAccountSource.getAllAccounts()).thenReturn(ImmutableList.of(account1, account2)); DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); GroupKey key1 = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .setAccount(AccountUtil.serialize(account1)) .build(); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); GroupKey key2 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .setAccount(AccountUtil.serialize(account2)) .build(); DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2); GroupKey key3 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_3) .setOwnerPackage(context.getPackageName()) .build(); writeDownloadedFileGroup(key1, fileGroup1); writeDownloadedFileGroup(key2, fileGroup2); writeDownloadedFileGroup(key3, fileGroup3); fileGroupManager.deleteRemovedAccountGroups().get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .containsExactly(getDownloadedKey(key1), getDownloadedKey(key2), getDownloadedKey(key3)); verifyNoInteractions(mockLogger); } @Test public void testDeleteRemovedAccountGroups_removedAccounts() throws Exception { Account account1 = new Account("name1", "type1"); Account account2 = new Account("name2", "type2"); when(mockAccountSource.getAllAccounts()).thenReturn(ImmutableList.of(account1)); DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); GroupKey key1 = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .setAccount(AccountUtil.serialize(account1)) .build(); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); GroupKey key2 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .setAccount(AccountUtil.serialize(account2)) .build(); DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2); GroupKey key3 = GroupKey.newBuilder() .setGroupName(TEST_GROUP_3) .setOwnerPackage(context.getPackageName()) .build(); writeDownloadedFileGroup(key1, fileGroup1); writeDownloadedFileGroup(key2, fileGroup2); writeDownloadedFileGroup(key3, fileGroup3); fileGroupManager.deleteRemovedAccountGroups().get(); assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .containsExactly(getDownloadedKey(key1), getDownloadedKey(key3)); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verifyNoMoreInteractions(mockLogger); } @Test public void testLogAndDeleteForMissingSharedFiles() throws Exception { resetFileGroupManager(fileGroupsMetadata, mockSharedFileManager); GroupKey downloadedGroupKeyWithFileMissing = GroupKey.newBuilder() .setGroupName(TEST_GROUP) .setOwnerPackage(context.getPackageName()) .setDownloaded(true) .build(); DataFileGroupInternal fileGroup1 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2); writeDownloadedFileGroup(downloadedGroupKeyWithFileMissing, fileGroup1); NewFileKey[] keys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup1); when(mockSharedFileManager.reVerifyFile(eq(keys[0]), eq(fileGroup1.getFile(0)))) .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException())); when(mockSharedFileManager.reVerifyFile(eq(keys[1]), eq(fileGroup1.getFile(1)))) .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException())); GroupKey pendingGroupKeyWithFileMissing = GroupKey.newBuilder() .setGroupName(TEST_GROUP_2) .setOwnerPackage(context.getPackageName()) .setDownloaded(false) .build(); DataFileGroupInternal fileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_2, 2); writePendingFileGroup(pendingGroupKeyWithFileMissing, fileGroup2); // Write only the first file metadata. NewFileKey[] keys2 = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup2); when(mockSharedFileManager.reVerifyFile(eq(keys2[0]), eq(fileGroup2.getFile(0)))) .thenReturn(Futures.immediateFailedFuture(new SharedFileMissingException())); // mockSharedFileManager returns "OK" when verifying second file. GroupKey groupKeyWithNoFileMissing = GroupKey.newBuilder() .setGroupName(TEST_GROUP_3) .setOwnerPackage(context.getPackageName()) .setDownloaded(true) .build(); DataFileGroupInternal fileGroup3 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP_3, 2); writeDownloadedFileGroup(groupKeyWithNoFileMissing, fileGroup3); // mockSharedFileManager always returns "OK" when verifying files. fileGroupManager.logAndDeleteForMissingSharedFiles().get(); if (flags.deleteFileGroupsWithFilesMissing()) { assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .containsExactly(groupKeyWithNoFileMissing); } else { assertThat(fileGroupsMetadata.getAllGroupKeys().get()) .containsExactly( downloadedGroupKeyWithFileMissing, pendingGroupKeyWithFileMissing, groupKeyWithNoFileMissing); } verify(mockLogger, times(2)) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verify(mockLogger) .logEventSampled( MddClientEvent.Code.EVENT_CODE_UNSPECIFIED, TEST_GROUP_2, /* fileGroupVersionNumber= */ 0, /* buildId= */ 0, /* variantId= */ ""); verifyNoMoreInteractions(mockLogger); } @Test public void getOnDeviceUri_shortcutsForSideloadedFiles_delegatesToSharedFileManagerOtherwise() throws Exception { // Ensure that sideloading is turned off flags.enableSideloading = Optional.of(true); // Create mixed group DataFileGroupInternal sideloadedGroup = DataFileGroupInternal.newBuilder() .setGroupName(TEST_GROUP) .addFile( DataFile.newBuilder() .setFileId("sideloaded_file") .setUrlToDownload("file:/test") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .addFile( DataFile.newBuilder() .setFileId("standard_file") .setUrlToDownload("https://url.to.download") .setChecksumType(DataFile.ChecksumType.NONE) .build()) .addFile( DataFile.newBuilder() .setFileId("inline_file") .setUrlToDownload("inlinefile:sha1:checksum") .setChecksum("checksum") .build()) .build(); // Write shared files so shared file manager can get uris NewFileKey[] newFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(sideloadedGroup); sharedFilesMetadata .write( newFileKeys[1], SharedFile.newBuilder() .setFileName(sideloadedGroup.getFile(1).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build()) .get(); sharedFilesMetadata .write( newFileKeys[2], SharedFile.newBuilder() .setFileName(sideloadedGroup.getFile(2).getFileId()) .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build()) .get(); assertThat( fileGroupManager .getOnDeviceUri(sideloadedGroup.getFile(0), sideloadedGroup) .get() .getScheme()) .isEqualTo("file"); assertThat( fileGroupManager .getOnDeviceUri(sideloadedGroup.getFile(1), sideloadedGroup) .get() .getScheme()) .isEqualTo("android"); assertThat( fileGroupManager .getOnDeviceUri(sideloadedGroup.getFile(2), sideloadedGroup) .get() .getScheme()) .isEqualTo("android"); } @Test public void testAddGroupForDownload_withExperimentationConfig() throws Exception { flags.enableDownloadStageExperimentIdPropagation = Optional.of(true); Long buildId = 999L; Integer experimentId = 12345; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(buildId) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); } @Test public void testAddGroupForDownload_withExperimentationConfig_overwritesPendingExperimentIds() throws Exception { flags.enableDownloadStageExperimentIdPropagation = Optional.of(true); long buildId = 999L; long buildId2 = 100L; int experimentId = 12345; int experimentId2 = 23456; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(buildId) .build(); DataFileGroupInternal dataFileGroup2 = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(buildId2) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); // Overwrite the group. The old experiment id should be deleted and the new experiment id should // be populated. assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup2).get()).isTrue(); } @Test public void testDownloadPendingGroup_withExperimentationConfig_updatesExperimentIdToDownloaded() throws Exception { flags.enableDownloadStageExperimentIdPropagation = Optional.of(true); int experimentIdDownloading = 12345; int experimentIdDownloaded = 23456; long buildId = 999L; ExtraHttpHeader extraHttpHeader = ExtraHttpHeader.newBuilder().setKey("user-agent").setValue("mdd-downloader").build(); // Write 1 group to the pending shared prefs. DataFileGroupInternal fileGroup = createDataFileGroup( TEST_GROUP, /* fileCount= */ 2, /* downloadAttemptCount= */ 3, /* newFilesReceivedTimestamp= */ testClock.currentTimeMillis() - 500L) .toBuilder() .setBuildId(buildId) .setOwnerPackage(context.getPackageName()) .setDownloadConditions(DownloadConditions.getDefaultInstance()) .setTrafficTag(TRAFFIC_TAG) .addGroupExtraHttpHeaders(extraHttpHeader) .build(); writePendingFileGroup(testKey, fileGroup); writeSharedFiles( sharedFilesMetadata, fileGroup, ImmutableList.of(FileStatus.DOWNLOAD_COMPLETE, FileStatus.DOWNLOAD_COMPLETE)); fileGroupManager .downloadFileGroup(testKey, DownloadConditions.getDefaultInstance(), noCustomValidation()) .get(); } @Test public void testRemoveFileGroup_withExperimentationConfig_removesExperimentIds() throws Exception { flags.enableDownloadStageExperimentIdPropagation = Optional.of(true); long buildId = 999L; int experimentId = 12345; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(buildId) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); fileGroupManager.removeFileGroup(testKey, /* pendingOnly= */ false).get(); } @Test public void testRemoveFileGroups_withExperimentationConfig_removesExperimentIds() throws Exception { flags.enableDownloadStageExperimentIdPropagation = Optional.of(true); long buildId = 999L; int experimentId = 12345; DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder() .setBuildId(buildId) .build(); assertThat(fileGroupManager.addGroupForDownload(testKey, dataFileGroup).get()).isTrue(); fileGroupManager.removeFileGroups(ImmutableList.of(testKey)).get(); } /** * Re-instantiates {@code fileGroupManager} with the injected parameters. * *

It can be used to work with the mocks for FileGroupsMetadata and/or SharedFileManager. */ private void resetFileGroupManager( FileGroupsMetadata fileGroupsMetadata, SharedFileManager sharedFileManager) throws Exception { resetFileGroupManager(this.mockLogger, fileGroupsMetadata, sharedFileManager); } private void resetFileGroupManager( EventLogger eventLogger, FileGroupsMetadata fileGroupsMetadata, SharedFileManager sharedFileManager) throws Exception { fileGroupManager = new FileGroupManager( context, eventLogger, mockSilentFeedback, fileGroupsMetadata, sharedFileManager, testClock, Optional.of(mockAccountSource), SEQUENTIAL_CONTROL_EXECUTOR, Optional.absent(), fileStorage, downloadStageManager, flags); } private static DataDownloadFileGroupStats.Builder createFileGroupDetails( DataFileGroupInternal fileGroup) { return DataDownloadFileGroupStats.newBuilder() .setOwnerPackage(fileGroup.getOwnerPackage()) .setFileGroupName(fileGroup.getGroupName()) .setFileGroupVersionNumber(fileGroup.getFileGroupVersionNumber()) .setBuildId(fileGroup.getBuildId()) .setVariantId(fileGroup.getVariantId()) .setFileCount(fileGroup.getFileCount()); } private static Void createMddDownloadLatency( int downloadAttemptCount, long downloadLatencyMs, long totalLatencyMs) { return null; } private static DataFileGroupInternal createDataFileGroup( String groupName, int fileCount, int downloadAttemptCount, long newFilesReceivedTimestamp) { return MddTestUtil.createDataFileGroupInternal(groupName, fileCount).toBuilder() .setBookkeeping( DataFileGroupBookkeeping.newBuilder() .setDownloadStartedCount(downloadAttemptCount) .setGroupNewFilesReceivedTimestamp(newFilesReceivedTimestamp)) .build(); } /** The file download succeeds so the new file status is DOWNLOAD_COMPLETE. */ private void fileDownloadSucceeds(NewFileKey key, Uri fileUri) { when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), eq(fileUri), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { SharedFile sharedFile = sharedFileManager.getSharedFile(key).get().toBuilder() .setFileStatus(FileStatus.DOWNLOAD_COMPLETE) .build(); sharedFilesMetadata.write(key, sharedFile).get(); return Futures.immediateVoidFuture(); } }); } /** * The file download fails so the new file status is DOWNLOAD_FAILED. If failureCode is not null, * the downloader returns a immediateFailedFuture; otherwise it returns an immediateVoidFuture. */ private void fileDownloadFails(NewFileKey key, Uri fileUri, DownloadResultCode failureCode) { when(mockDownloader.startDownloading( any(String.class), any(GroupKey.class), anyInt(), anyLong(), any(String.class), eq(fileUri), any(String.class), anyInt(), any(DownloadConditions.class), isA(DownloaderCallbackImpl.class), anyInt(), anyList())) .then( new Answer>() { @Override public ListenableFuture answer(InvocationOnMock invocation) throws Throwable { SharedFile sharedFile = sharedFileManager.getSharedFile(key).get().toBuilder() .setFileStatus(FileStatus.DOWNLOAD_FAILED) .build(); sharedFilesMetadata.write(key, sharedFile).get(); if (failureCode == null) { return Futures.immediateVoidFuture(); } return Futures.immediateFailedFuture( DownloadException.builder().setDownloadResultCode(failureCode).build()); } }); } private DataFileGroupInternal readPendingFileGroup(GroupKey key) throws Exception { GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build(); return fileGroupsMetadata.read(duplicateGroupKey).get(); } private DataFileGroupInternal readDownloadedFileGroup(GroupKey key) throws Exception { GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build(); return fileGroupsMetadata.read(duplicateGroupKey).get(); } private void writePendingFileGroup(GroupKey key, DataFileGroupInternal group) throws Exception { GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build(); fileGroupsMetadata.write(duplicateGroupKey, group).get(); } private void writeDownloadedFileGroup(GroupKey key, DataFileGroupInternal group) throws Exception { GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(true).build(); fileGroupsMetadata.write(duplicateGroupKey, group).get(); } private void verifyAddGroupForDownloadWritesMetadata( GroupKey key, DataFileGroupInternal group, long expectedTimestamp) throws Exception { GroupKey duplicateGroupKey = key.toBuilder().setDownloaded(false).build(); DataFileGroupInternal updatedFileGroup = setReceivedTimeStampWithFeatureOn(group, expectedTimestamp); assertThat(fileGroupsMetadata.read(duplicateGroupKey).get()).isEqualTo(updatedFileGroup); } private static GroupKey getPendingKey(GroupKey key) { return key.toBuilder().setDownloaded(false).build(); } private static GroupKey getDownloadedKey(GroupKey key) { return key.toBuilder().setDownloaded(true).build(); } private static DataFileGroupInternal setReceivedTimeStampWithFeatureOn( DataFileGroupInternal dataFileGroup, long elapsedTime) { DataFileGroupBookkeeping bookkeeping = dataFileGroup.getBookkeeping().toBuilder() .setGroupNewFilesReceivedTimestamp(elapsedTime) .build(); return dataFileGroup.toBuilder().setBookkeeping(bookkeeping).build(); } /** * Simulates the download of the file {@code dataFile} by writing a file with name {@code * fileName}. */ private File simulateDownload(DataFile dataFile, String fileName) throws IOException { File onDeviceFile = new File(publicDirectory, fileName); byte[] bytes = new byte[dataFile.getByteSize()]; try (FileOutputStream writer = new FileOutputStream(onDeviceFile)) { writer.write(bytes); } return onDeviceFile; } private List getOnDeviceUrisForFileGroup(DataFileGroupInternal fileGroup) { ArrayList uriList = new ArrayList<>(fileGroup.getFileCount()); NewFileKey[] newFileKeys = MddTestUtil.createFileKeysForDataFileGroupInternal(fileGroup); for (int i = 0; i < newFileKeys.length; i++) { NewFileKey newFileKey = newFileKeys[i]; DataFile dataFile = fileGroup.getFile(i); uriList.add( DirectoryUtil.getOnDeviceUri( context, newFileKey.getAllowedReaders(), dataFile.getFileId(), newFileKey.getChecksum(), mockSilentFeedback, /* instanceId= */ Optional.absent(), /* androidShared= */ false)); } return uriList; } private AsyncFunction noCustomValidation() { return unused -> Futures.immediateFuture(true); } }