• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.libraries.mobiledatadownload.internal;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.util.concurrent.Futures.immediateFuture;
20 import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
21 import static java.util.concurrent.TimeUnit.DAYS;
22 import static org.junit.Assert.assertThrows;
23 import static org.junit.Assume.assumeTrue;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.anyBoolean;
26 import static org.mockito.ArgumentMatchers.anyList;
27 import static org.mockito.ArgumentMatchers.anyLong;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.ArgumentMatchers.isA;
30 import static org.mockito.ArgumentMatchers.isNull;
31 import static org.mockito.Mockito.doThrow;
32 import static org.mockito.Mockito.inOrder;
33 import static org.mockito.Mockito.never;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.verifyNoInteractions;
37 import static org.mockito.Mockito.verifyNoMoreInteractions;
38 import static org.mockito.Mockito.when;
39 
40 import android.content.Context;
41 import android.content.SharedPreferences;
42 import android.net.Uri;
43 import androidx.test.core.app.ApplicationProvider;
44 import com.google.mobiledatadownload.internal.MetadataProto.DataFile;
45 import com.google.mobiledatadownload.internal.MetadataProto.DataFile.ChecksumType;
46 import com.google.mobiledatadownload.internal.MetadataProto.DataFileGroupInternal;
47 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions;
48 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceNetworkPolicy;
49 import com.google.mobiledatadownload.internal.MetadataProto.DownloadConditions.DeviceStoragePolicy;
50 import com.google.mobiledatadownload.internal.MetadataProto.GroupKey;
51 import com.google.android.libraries.mobiledatadownload.DownloadException;
52 import com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode;
53 import com.google.android.libraries.mobiledatadownload.FileSource;
54 import com.google.android.libraries.mobiledatadownload.SilentFeedback;
55 import com.google.android.libraries.mobiledatadownload.file.common.testing.TemporaryUri;
56 import com.google.android.libraries.mobiledatadownload.internal.FileGroupManager.GroupDownloadStatus;
57 import com.google.android.libraries.mobiledatadownload.internal.Migrations.FileKeyVersion;
58 import com.google.android.libraries.mobiledatadownload.internal.collect.GroupKeyAndGroup;
59 import com.google.android.libraries.mobiledatadownload.internal.experimentation.DownloadStageManager;
60 import com.google.android.libraries.mobiledatadownload.internal.experimentation.NoOpDownloadStageManager;
61 import com.google.android.libraries.mobiledatadownload.internal.logging.EventLogger;
62 import com.google.android.libraries.mobiledatadownload.internal.logging.FileGroupStatsLogger;
63 import com.google.android.libraries.mobiledatadownload.internal.logging.LoggingStateStore;
64 import com.google.android.libraries.mobiledatadownload.internal.logging.NetworkLogger;
65 import com.google.android.libraries.mobiledatadownload.internal.logging.StorageLogger;
66 import com.google.android.libraries.mobiledatadownload.internal.util.FileGroupUtil;
67 import com.google.android.libraries.mobiledatadownload.internal.util.SharedPreferencesUtil;
68 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource;
69 import com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies;
70 import com.google.android.libraries.mobiledatadownload.testing.TestFlags;
71 import com.google.common.base.Optional;
72 import com.google.common.collect.ImmutableList;
73 import com.google.common.collect.ImmutableMap;
74 import com.google.common.labs.concurrent.LabsFutures;
75 import com.google.common.util.concurrent.AsyncFunction;
76 import com.google.common.util.concurrent.Futures;
77 import com.google.common.util.concurrent.ListenableFuture;
78 import com.google.mobiledatadownload.LogEnumsProto.MddClientEvent;
79 import com.google.mobiledatadownload.TransformProto.CompressTransform;
80 import com.google.mobiledatadownload.TransformProto.Transform;
81 import com.google.mobiledatadownload.TransformProto.Transforms;
82 import com.google.mobiledatadownload.TransformProto.ZipTransform;
83 import com.google.protobuf.ByteString;
84 import java.io.IOException;
85 import java.util.List;
86 import java.util.Random;
87 import java.util.concurrent.ExecutionException;
88 import java.util.concurrent.Executor;
89 import java.util.concurrent.Executors;
90 import org.junit.After;
91 import org.junit.Before;
92 import org.junit.Rule;
93 import org.junit.Test;
94 import org.junit.runner.RunWith;
95 import org.mockito.ArgumentCaptor;
96 import org.mockito.Captor;
97 import org.mockito.InOrder;
98 import org.mockito.Mock;
99 import org.mockito.junit.MockitoJUnit;
100 import org.mockito.junit.MockitoRule;
101 import org.robolectric.RobolectricTestRunner;
102 import org.robolectric.annotation.LooperMode;
103 
104 // The LooperMode Mode.PAUSED fixes buggy behavior in the legacy looper implementation that can lead
105 // to deadlock in some cases. See documentation at:
106 // http://robolectric.org/javadoc/4.3/org/robolectric/annotation/LooperMode.Mode.html for more
107 // information.
108 @RunWith(RobolectricTestRunner.class)
109 @LooperMode(LooperMode.Mode.PAUSED)
110 public class MobileDataDownloadManagerTest {
111 
112   private static final String TEST_GROUP = "test-group";
113   private static final GroupKey TEST_KEY =
114       FileGroupUtil.createGroupKey(TEST_GROUP, "com.google.android.gms");
115   private static final Executor CONTROL_EXECUTOR = Executors.newCachedThreadPool();
116 
117   private static final int DEFAULT_DAYS_SINCE_LAST_LOG = 1;
118 
119   // Note: We can't make those android uris static variable since the Uri.parse will fail
120   // with initialization.
121   private final Uri fileUri1 = Uri.parse(MddTestUtil.FILE_URI + "1");
122   private final Uri fileUri2 = Uri.parse(MddTestUtil.FILE_URI + "2");
123 
124   private static final String HOST_APP_LOG_SOURCE = "HOST_APP_LOG_SOURCE";
125   private static final String HOST_APP_PRIMES_LOG_SOURCE = "HOST_APP_PRIMES_LOG_SOURCE";
126 
127   private Context context;
128   private MobileDataDownloadManager mddManager;
129   private final TestFlags flags = new TestFlags();
130 
131   @Rule(order = 2)
132   public final TemporaryUri tmpUri = new TemporaryUri();
133 
134   @Rule(order = 3)
135   public final MockitoRule mocks = MockitoJUnit.rule();
136 
137   @Mock EventLogger mockLogger;
138   @Mock SharedFileManager mockSharedFileManager;
139   @Mock SharedFilesMetadata mockSharedFilesMetadata;
140   @Mock FileGroupManager mockFileGroupManager;
141   @Mock FileGroupsMetadata mockFileGroupsMetadata;
142   @Mock ExpirationHandler mockExpirationHandler;
143   @Mock SilentFeedback mockSilentFeedback;
144   @Mock StorageLogger mockStorageLogger;
145   @Mock FileGroupStatsLogger mockFileGroupStatsLogger;
146   @Mock NetworkLogger mockNetworkLogger;
147 
148   private LoggingStateStore loggingStateStore;
149   private DownloadStageManager downloadStageManager;
150   private FakeTimeSource testClock;
151 
152   @Captor ArgumentCaptor<List<GroupKey>> groupKeyListCaptor;
153 
154   @Before
setUp()155   public void setUp() throws Exception {
156     context = ApplicationProvider.getApplicationContext();
157     this.testClock = new FakeTimeSource();
158     testClock.advance(1, DAYS);
159 
160     loggingStateStore =
161         MddTestDependencies.LoggingStateStoreImpl.SHARED_PREFERENCES.loggingStateStore(
162             context, Optional.absent(), testClock, CONTROL_EXECUTOR, new Random());
163 
164     loggingStateStore.getAndResetDaysSinceLastMaintenance().get();
165     testClock.advance(1, DAYS); // The next call into logging state store will return 1
166 
167     downloadStageManager = new NoOpDownloadStageManager();
168 
169     mddManager =
170         new MobileDataDownloadManager(
171             context,
172             mockLogger,
173             mockSharedFileManager,
174             mockSharedFilesMetadata,
175             mockFileGroupManager,
176             mockFileGroupsMetadata,
177             mockExpirationHandler,
178             mockSilentFeedback,
179             mockStorageLogger,
180             mockFileGroupStatsLogger,
181             mockNetworkLogger,
182             Optional.absent(),
183             CONTROL_EXECUTOR,
184             flags,
185             loggingStateStore,
186             downloadStageManager);
187 
188     // Enable migrations so that init doesn't run all migrations before each test.
189     setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, true);
190 
191     when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(true));
192     when(mockSharedFileManager.clear()).thenReturn(Futures.immediateFuture(null));
193     when(mockSharedFileManager.cancelDownload(any())).thenReturn(Futures.immediateFuture(null));
194     when(mockSharedFileManager.cancelDownloadAndClear()).thenReturn(Futures.immediateFuture(null));
195 
196     when(mockSharedFilesMetadata.init()).thenReturn(Futures.immediateFuture(true));
197     when(mockSharedFilesMetadata.clear()).thenReturn(immediateVoidFuture());
198 
199     when(mockFileGroupsMetadata.init()).thenReturn(Futures.immediateFuture(null));
200     when(mockFileGroupsMetadata.clear()).thenReturn(Futures.immediateFuture(null));
201     when(mockFileGroupsMetadata.getAllStaleGroups())
202         .thenReturn(Futures.immediateFuture(ImmutableList.of()));
203     when(mockFileGroupsMetadata.getAllFreshGroups())
204         .thenReturn(Futures.immediateFuture(ImmutableList.of()));
205   }
206 
207   @After
tearDown()208   public void tearDown() throws Exception {
209     mddManager.clear().get();
210   }
211 
212   @Test
init_offroadDownloaderMigration()213   public void init_offroadDownloaderMigration() throws Exception {
214     setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, false);
215 
216     mddManager.init().get();
217 
218     verify(mockSharedFileManager).clear();
219   }
220 
221   @Test
init_offroadDownloaderMigration_onlyOnce()222   public void init_offroadDownloaderMigration_onlyOnce() throws Exception {
223     setMigrationState(MobileDataDownloadManager.MDD_MIGRATED_TO_OFFROAD, false);
224 
225     mddManager.init().get();
226     mddManager.init().get();
227 
228     verify(mockSharedFileManager, times(1)).clear();
229   }
230 
231   @Test
initDoesNotClearsIfInternalInitSucceeds()232   public void initDoesNotClearsIfInternalInitSucceeds() throws Exception {
233     when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(true));
234 
235     mddManager.init().get();
236 
237     verify(mockSharedFileManager, times(0)).clear();
238   }
239 
240   @Test
initClearsIfInternalInitFails()241   public void initClearsIfInternalInitFails() throws Exception {
242     when(mockSharedFileManager.init()).thenReturn(Futures.immediateFuture(false));
243 
244     mddManager.init().get();
245 
246     verify(mockSharedFileManager).clear();
247   }
248 
249   @Test
testAddGroupForDownload()250   public void testAddGroupForDownload() throws Exception {
251     // This tests that the default value of {allowed_readers, allowed_readers_enum} is to allow
252     // access to all 1p google apps.
253     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
254     when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), eq(dataFileGroup)))
255         .thenReturn(Futures.immediateFuture(true));
256     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
257         .thenReturn(immediateFuture(dataFileGroup));
258     when(mockFileGroupManager.verifyGroupDownloaded(
259             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
260         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
261     when(mockFileGroupsMetadata.getAllFreshGroups())
262         .thenReturn(
263             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
264 
265     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
266     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
267     verify(mockFileGroupManager)
268         .verifyGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any());
269     verifyNoInteractions(mockLogger);
270 
271     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
272   }
273 
274   @Test
testAddGroupForDownload_compressedFile()275   public void testAddGroupForDownload_compressedFile() throws Exception {
276     Migrations.setCurrentVersion(context, FileKeyVersion.USE_CHECKSUM_ONLY);
277     DataFileGroupInternal.Builder fileGroupBuilder =
278         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
279     DataFileGroupInternal dataFileGroup =
280         fileGroupBuilder
281             .setFile(
282                 0,
283                 fileGroupBuilder.getFile(0).toBuilder()
284                     .setDownloadedFileChecksum("downloadchecksum")
285                     .setDownloadTransforms(
286                         Transforms.newBuilder()
287                             .addTransform(
288                                 Transform.newBuilder()
289                                     .setCompress(CompressTransform.getDefaultInstance()))))
290             .build();
291     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
292         .thenReturn(Futures.immediateFuture(true));
293     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
294         .thenReturn(immediateFuture(dataFileGroup));
295     when(mockFileGroupManager.verifyGroupDownloaded(
296             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
297         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
298     when(mockFileGroupsMetadata.getAllFreshGroups())
299         .thenReturn(
300             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
301 
302     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
303     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
304     verify(mockFileGroupManager)
305         .verifyGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any());
306     verifyNoInteractions(mockLogger);
307 
308     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
309   }
310 
311   @Test
testAddGroupForDownload_deltaFile()312   public void testAddGroupForDownload_deltaFile() throws Exception {
313     Migrations.setCurrentVersion(context, FileKeyVersion.USE_CHECKSUM_ONLY);
314     DataFileGroupInternal dataFileGroup =
315         MddTestUtil.createFileGroupInternalWithDeltaFile(TEST_GROUP);
316     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
317         .thenReturn(Futures.immediateFuture(true));
318     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
319         .thenReturn(immediateFuture(dataFileGroup));
320     when(mockFileGroupManager.verifyGroupDownloaded(
321             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
322         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
323     when(mockFileGroupsMetadata.getAllFreshGroups())
324         .thenReturn(
325             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
326 
327     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
328     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
329     verify(mockFileGroupManager)
330         .verifyGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any());
331     verifyNoInteractions(mockLogger);
332 
333     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
334   }
335 
336   @Test
testAddGroupForDownload_downloadImmediate()337   public void testAddGroupForDownload_downloadImmediate() throws Exception {
338     // This tests that the default value of {allowed_readers, allowed_readers_enum} is to allow
339     // access to all 1p google apps.
340     DataFileGroupInternal dataFileGroup =
341         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
342             .setVariantId("testVariant")
343             .setBuildId(10)
344             .build();
345     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
346         .thenReturn(Futures.immediateFuture(true));
347     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
348         .thenReturn(immediateFuture(dataFileGroup));
349     when(mockFileGroupManager.verifyGroupDownloaded(
350             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
351         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.DOWNLOADED));
352     when(mockFileGroupsMetadata.getAllFreshGroups())
353         .thenReturn(
354             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
355 
356     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
357     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
358     verify(mockFileGroupManager)
359         .verifyGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any());
360     verify(mockLogger)
361         .logEventSampled(
362             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
363             TEST_GROUP,
364             /* fileGroupVersionNumber= */ 0,
365             /* buildId= */ dataFileGroup.getBuildId(),
366             /* variantId= */ dataFileGroup.getVariantId());
367 
368     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
369   }
370 
371   @Test
testAddGroupForDownload_throwsIOException()372   public void testAddGroupForDownload_throwsIOException() throws Exception {
373     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
374     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
375         .thenThrow(new IOException());
376 
377     // assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
378     ExecutionException exception =
379         assertThrows(
380             ExecutionException.class,
381             () -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
382     assertThat(exception).hasCauseThat().isInstanceOf(IOException.class);
383     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
384     verify(mockSilentFeedback).send(isA(IOException.class), isA(String.class));
385     verifyNoInteractions(mockLogger);
386   }
387 
388   @Test
testAddGroupForDownload_throwsUninstalledAppException()389   public void testAddGroupForDownload_throwsUninstalledAppException() throws Exception {
390     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
391     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
392         .thenThrow(new UninstalledAppException());
393 
394     // assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
395     ExecutionException exception =
396         assertThrows(
397             ExecutionException.class,
398             () -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
399     assertThat(exception).hasCauseThat().isInstanceOf(UninstalledAppException.class);
400     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
401     verifyNoInteractions(mockLogger);
402   }
403 
404   @Test
testAddGroupForDownload_throwsExpiredFileGroupException()405   public void testAddGroupForDownload_throwsExpiredFileGroupException() throws Exception {
406     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
407     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
408         .thenThrow(new ExpiredFileGroupException());
409 
410     // assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
411     ExecutionException exception =
412         assertThrows(
413             ExecutionException.class,
414             () -> mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get());
415     assertThat(exception).hasCauseThat().isInstanceOf(ExpiredFileGroupException.class);
416     verify(mockFileGroupManager).addGroupForDownload(TEST_KEY, dataFileGroup);
417     verifyNoInteractions(mockLogger);
418   }
419 
420   @Test
testAddGroupForDownload_multipleCallsSameGroup()421   public void testAddGroupForDownload_multipleCallsSameGroup() throws Exception {
422     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
423     when(mockFileGroupManager.addGroupForDownload(TEST_KEY, dataFileGroup))
424         .thenReturn(Futures.immediateFuture(true), Futures.immediateFuture(false));
425     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
426         .thenReturn(immediateFuture(dataFileGroup));
427     when(mockFileGroupManager.verifyGroupDownloaded(
428             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
429         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
430     when(mockFileGroupsMetadata.getAllFreshGroups())
431         .thenReturn(
432             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
433 
434     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
435     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
436     verify(mockFileGroupManager, times(2)).addGroupForDownload(TEST_KEY, dataFileGroup);
437     verify(mockFileGroupManager, times(1))
438         .verifyGroupDownloaded(eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any());
439     verifyNoInteractions(mockExpirationHandler);
440     verifyNoInteractions(mockLogger);
441   }
442 
443   @Test
testAddGroupForDownload_isValidGroup()444   public void testAddGroupForDownload_isValidGroup() throws Exception {
445     DataFileGroupInternal dataFileGroup =
446         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder()
447             .setGroupName("")
448             .setVariantId("testVariant")
449             .setBuildId(10)
450             .build();
451     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
452     verifyNoInteractions(mockFileGroupManager);
453 
454     verify(mockLogger)
455         .logEventSampled(
456             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
457             "",
458             /* fileGroupVersionNumber= */ 0,
459             /* buildId= */ dataFileGroup.getBuildId(),
460             /* variantId= */ dataFileGroup.getVariantId());
461   }
462 
463   @Test
testAddGroupForDownload_noChecksum()464   public void testAddGroupForDownload_noChecksum() throws Exception {
465     DataFileGroupInternal.Builder fileGroupBuilder =
466         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
467     DataFileGroupInternal dataFileGroup =
468         fileGroupBuilder
469             .setFile(
470                 0,
471                 fileGroupBuilder.getFile(0).toBuilder()
472                     .setChecksumType(ChecksumType.NONE)
473                     .setChecksum(""))
474             .build();
475 
476     ArgumentCaptor<DataFileGroupInternal> dataFileGroupCaptor =
477         ArgumentCaptor.forClass(DataFileGroupInternal.class);
478 
479     when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), dataFileGroupCaptor.capture()))
480         .thenReturn(Futures.immediateFuture(true));
481     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
482         .thenReturn(Futures.immediateFuture(dataFileGroup));
483     when(mockFileGroupManager.verifyGroupDownloaded(
484             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
485         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
486     when(mockFileGroupsMetadata.getAllFreshGroups())
487         .thenReturn(
488             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
489 
490     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
491     verifyNoInteractions(mockLogger);
492 
493     DataFileGroupInternal capturedDataFileGroup = dataFileGroupCaptor.getValue();
494 
495     assertThat(capturedDataFileGroup.getFileCount()).isEqualTo(1);
496     DataFile dataFile = capturedDataFileGroup.getFile(0);
497     // Checksum of the Url.
498     assertThat(dataFile.getChecksum()).isEqualTo("0d79849a839d83fbc53e3bfe794ec38a305b7220");
499   }
500 
501   @Test
testAddGroupForDownload_noChecksumWithZipTransform()502   public void testAddGroupForDownload_noChecksumWithZipTransform() throws Exception {
503     DataFileGroupInternal.Builder fileGroupBuilder =
504         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
505     DataFileGroupInternal dataFileGroup =
506         fileGroupBuilder
507             .setFile(
508                 0,
509                 fileGroupBuilder.getFile(0).toBuilder()
510                     .setChecksumType(ChecksumType.NONE)
511                     .setChecksum("")
512                     .setDownloadedFileChecksum("")
513                     .setDownloadTransforms(
514                         Transforms.newBuilder()
515                             .addTransform(
516                                 Transform.newBuilder()
517                                     .setZip(ZipTransform.newBuilder().setTarget("*")))))
518             .build();
519 
520     ArgumentCaptor<DataFileGroupInternal> dataFileGroupCaptor =
521         ArgumentCaptor.forClass(DataFileGroupInternal.class);
522 
523     when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), dataFileGroupCaptor.capture()))
524         .thenReturn(Futures.immediateFuture(true));
525     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
526         .thenReturn(immediateFuture(dataFileGroup));
527     when(mockFileGroupManager.verifyGroupDownloaded(
528             eq(TEST_KEY), eq(dataFileGroup), anyBoolean(), any(), any()))
529         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
530     when(mockFileGroupsMetadata.getAllFreshGroups())
531         .thenReturn(
532             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, dataFileGroup))));
533 
534     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isTrue();
535     verifyNoInteractions(mockLogger);
536 
537     DataFileGroupInternal capturedDataFileGroup = dataFileGroupCaptor.getValue();
538 
539     assertThat(capturedDataFileGroup.getFileCount()).isEqualTo(1);
540     DataFile dataFile = capturedDataFileGroup.getFile(0);
541     // Checksum of url is propagated to downloaded file checksum if data file has zip transform.
542     assertThat(dataFile.getChecksum()).isEmpty();
543     assertThat(dataFile.getDownloadedFileChecksum())
544         .isEqualTo("0d79849a839d83fbc53e3bfe794ec38a305b7220");
545   }
546 
547   @Test
testAddGroupForDownload_noChecksumAndNotSetChecksumType()548   public void testAddGroupForDownload_noChecksumAndNotSetChecksumType() throws Exception {
549     DataFileGroupInternal.Builder fileGroupBuilder =
550         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1).toBuilder();
551     // Not setting ChecksumType.NONE
552     DataFileGroupInternal dataFileGroup =
553         fileGroupBuilder
554             .setFile(0, fileGroupBuilder.getFile(0).toBuilder().setChecksum(""))
555             .build();
556 
557     assertThat(mddManager.addGroupForDownload(TEST_KEY, dataFileGroup).get()).isFalse();
558     verify(mockLogger)
559         .logEventSampled(
560             MddClientEvent.Code.EVENT_CODE_UNSPECIFIED,
561             TEST_GROUP,
562             /* fileGroupVersionNumber= */ 0,
563             /* buildId= */ 0,
564             /* variantId= */ "");
565     verifyNoInteractions(mockFileGroupManager);
566   }
567 
568   @Test
testAddGroupForDownload_sideloadedFile_onlyWhenSideloadingIsEnabled()569   public void testAddGroupForDownload_sideloadedFile_onlyWhenSideloadingIsEnabled()
570       throws Exception {
571     // Create sideloaded group
572     DataFileGroupInternal sideloadedGroup =
573         DataFileGroupInternal.newBuilder()
574             .setGroupName(TEST_GROUP)
575             .addFile(
576                 DataFile.newBuilder()
577                     .setFileId("sideloaded_file")
578                     .setUrlToDownload("file:/test")
579                     .setChecksumType(DataFile.ChecksumType.NONE)
580                     .build())
581             .build();
582 
583     when(mockFileGroupManager.addGroupForDownload(eq(TEST_KEY), any()))
584         .thenReturn(Futures.immediateFuture(true));
585     when(mockFileGroupManager.getFileGroup(eq(TEST_KEY), anyBoolean()))
586         .thenReturn(immediateFuture(sideloadedGroup));
587     when(mockFileGroupManager.verifyGroupDownloaded(
588             eq(TEST_KEY), eq(sideloadedGroup), anyBoolean(), any(), any()))
589         .thenReturn(Futures.immediateFuture(GroupDownloadStatus.PENDING));
590     when(mockFileGroupsMetadata.getAllFreshGroups())
591         .thenReturn(
592             immediateFuture(ImmutableList.of(GroupKeyAndGroup.create(TEST_KEY, sideloadedGroup))));
593 
594     {
595       // Force sideloading off
596       flags.enableSideloading = Optional.of(false);
597 
598       assertThat(mddManager.addGroupForDownload(TEST_KEY, sideloadedGroup).get()).isFalse();
599     }
600 
601     {
602       // Force sideloading on
603       flags.enableSideloading = Optional.of(true);
604 
605       assertThat(mddManager.addGroupForDownload(TEST_KEY, sideloadedGroup).get()).isTrue();
606     }
607   }
608 
609   @Test
testRemoveFileGroup()610   public void testRemoveFileGroup() throws Exception {
611     GroupKey groupKey =
612         GroupKey.newBuilder()
613             .setGroupName(TEST_GROUP)
614             .setOwnerPackage(context.getPackageName())
615             .build();
616     when(mockFileGroupManager.removeFileGroup(eq(groupKey), eq(false)))
617         .thenReturn(Futures.immediateFuture(null /* Void */));
618 
619     mddManager.removeFileGroup(groupKey, /* pendingOnly= */ false).get();
620 
621     verify(mockFileGroupManager).removeFileGroup(groupKey, /* pendingOnly= */ false);
622     verifyNoMoreInteractions(mockFileGroupManager);
623     verifyNoInteractions(mockLogger);
624   }
625 
626   @Test
testRemoveFileGroup_onFailure()627   public void testRemoveFileGroup_onFailure() throws Exception {
628     GroupKey groupKey =
629         GroupKey.newBuilder()
630             .setGroupName(TEST_GROUP)
631             .setOwnerPackage(context.getPackageName())
632             .build();
633     doThrow(new IOException())
634         .when(mockFileGroupManager)
635         .removeFileGroup(groupKey, /* pendingOnly= */ false);
636 
637     ExecutionException ex =
638         assertThrows(
639             ExecutionException.class,
640             mddManager.removeFileGroup(groupKey, /* pendingOnly= */ false)::get);
641     assertThat(ex).hasCauseThat().isInstanceOf(IOException.class);
642 
643     verify(mockFileGroupManager).removeFileGroup(groupKey, /* pendingOnly= */ false);
644     verifyNoMoreInteractions(mockFileGroupManager);
645     verifyNoInteractions(mockLogger);
646   }
647 
648   @Test
testRemoveFileGroups()649   public void testRemoveFileGroups() throws Exception {
650     GroupKey groupKey1 =
651         GroupKey.newBuilder()
652             .setGroupName(TEST_GROUP)
653             .setOwnerPackage(context.getPackageName())
654             .build();
655     GroupKey groupKey2 =
656         GroupKey.newBuilder()
657             .setGroupName(TEST_GROUP + "_2")
658             .setOwnerPackage(context.getPackageName())
659             .build();
660 
661     when(mockFileGroupManager.removeFileGroups(groupKeyListCaptor.capture()))
662         .thenReturn(Futures.immediateVoidFuture());
663 
664     mddManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get();
665 
666     verify(mockFileGroupManager).removeFileGroups(anyList());
667     List<GroupKey> groupKeyListCapture = groupKeyListCaptor.getValue();
668     assertThat(groupKeyListCapture).hasSize(2);
669     assertThat(groupKeyListCapture).contains(groupKey1);
670     assertThat(groupKeyListCapture).contains(groupKey2);
671   }
672 
673   @Test
testRemoveFileGroups_onFailure()674   public void testRemoveFileGroups_onFailure() throws Exception {
675     GroupKey groupKey1 =
676         GroupKey.newBuilder()
677             .setGroupName(TEST_GROUP)
678             .setOwnerPackage(context.getPackageName())
679             .build();
680     GroupKey groupKey2 =
681         GroupKey.newBuilder()
682             .setGroupName(TEST_GROUP + "_2")
683             .setOwnerPackage(context.getPackageName())
684             .build();
685 
686     when(mockFileGroupManager.removeFileGroups(groupKeyListCaptor.capture()))
687         .thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
688 
689     ExecutionException ex =
690         assertThrows(
691             ExecutionException.class,
692             () -> mddManager.removeFileGroups(ImmutableList.of(groupKey1, groupKey2)).get());
693     assertThat(ex).hasMessageThat().contains("Test failure");
694 
695     verify(mockFileGroupManager).removeFileGroups(anyList());
696     List<GroupKey> groupKeyListCapture = groupKeyListCaptor.getValue();
697     assertThat(groupKeyListCapture).hasSize(2);
698     assertThat(groupKeyListCapture).contains(groupKey1);
699     assertThat(groupKeyListCapture).contains(groupKey2);
700   }
701 
702   @Test
testGetDownloadedGroup()703   public void testGetDownloadedGroup() throws Exception {
704     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
705     when(mockFileGroupManager.getFileGroup(TEST_KEY, true))
706         .thenReturn(Futures.immediateFuture(dataFileGroup));
707 
708     DataFileGroupInternal completedDataFileGroup = mddManager.getFileGroup(TEST_KEY, true).get();
709     MddTestUtil.assertMessageEquals(dataFileGroup, completedDataFileGroup);
710     verifyNoInteractions(mockLogger);
711   }
712 
713   @Test
testGetDataFileUri()714   public void testGetDataFileUri() throws Exception {
715     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
716 
717     when(mockFileGroupManager.getOnDeviceUris(dataFileGroup))
718         .thenReturn(
719             Futures.immediateFuture(
720                 ImmutableMap.of(
721                     dataFileGroup.getFile(0), fileUri1, dataFileGroup.getFile(1), fileUri2)));
722 
723     assertThat(
724             mddManager
725                 .getDataFileUri(
726                     dataFileGroup.getFile(0), dataFileGroup, /* verifyIsolatedStructure= */ true)
727                 .get())
728         .isEqualTo(fileUri1);
729     assertThat(
730             mddManager
731                 .getDataFileUri(
732                     dataFileGroup.getFile(1), dataFileGroup, /* verifyIsolatedStructure= */ true)
733                 .get())
734         .isEqualTo(fileUri2);
735   }
736 
737   @Test
testGetDataFileUri_readTransform()738   public void testGetDataFileUri_readTransform() throws Exception {
739     DataFileGroupInternal dataFileGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2);
740 
741     Transforms compressTransform =
742         Transforms.newBuilder()
743             .addTransform(
744                 Transform.newBuilder().setCompress(CompressTransform.getDefaultInstance()))
745             .build();
746     dataFileGroup =
747         dataFileGroup.toBuilder()
748             .setFile(0, dataFileGroup.getFile(0).toBuilder().setReadTransforms(compressTransform))
749             .build();
750 
751     when(mockFileGroupManager.getOnDeviceUris(dataFileGroup))
752         .thenReturn(
753             Futures.immediateFuture(
754                 ImmutableMap.of(
755                     dataFileGroup.getFile(0), fileUri1, dataFileGroup.getFile(1), fileUri2)));
756 
757     assertThat(
758             mddManager
759                 .getDataFileUri(
760                     dataFileGroup.getFile(0), dataFileGroup, /* verifyIsolatedStructure= */ true)
761                 .get())
762         .isEqualTo(fileUri1.buildUpon().encodedFragment("transform=compress").build());
763     assertThat(
764             mddManager
765                 .getDataFileUri(
766                     dataFileGroup.getFile(1), dataFileGroup, /* verifyIsolatedStructure= */ true)
767                 .get())
768         .isEqualTo(fileUri2);
769   }
770 
771   @Test
testGetDataFileUri_relativeFilePaths()772   public void testGetDataFileUri_relativeFilePaths() throws Exception {
773     DataFile relativePathFile = MddTestUtil.createRelativePathDataFile("file", 1, "test");
774     DataFileGroupInternal testFileGroup =
775         DataFileGroupInternal.newBuilder()
776             .setGroupName(TEST_GROUP)
777             .setPreserveFilenamesAndIsolateFiles(true)
778             .addFile(relativePathFile)
779             .build();
780 
781     Uri symlinkedUri =
782         FileGroupUtil.getIsolatedFileUri(
783             context, Optional.absent(), relativePathFile, testFileGroup);
784 
785     when(mockFileGroupManager.getOnDeviceUris(testFileGroup))
786         .thenReturn(Futures.immediateFuture(ImmutableMap.of(testFileGroup.getFile(0), fileUri1)));
787     when(mockFileGroupManager.getIsolatedFileUris(testFileGroup))
788         .thenReturn(ImmutableMap.of(testFileGroup.getFile(0), symlinkedUri));
789     when(mockFileGroupManager.verifyIsolatedFileUris(any(), any()))
790         .thenReturn(ImmutableMap.of(testFileGroup.getFile(0), symlinkedUri));
791 
792     assertThat(
793             mddManager
794                 .getDataFileUri(
795                     relativePathFile, testFileGroup, /* verifyIsolatedStructure= */ true)
796                 .get())
797         .isEqualTo(symlinkedUri);
798   }
799 
800   @Test
testGetDataFileUri_whenSymlinkRequiredButNotPresent_returnsNull()801   public void testGetDataFileUri_whenSymlinkRequiredButNotPresent_returnsNull() throws Exception {
802     DataFile relativePathFile = MddTestUtil.createRelativePathDataFile("file", 1, "test");
803     DataFileGroupInternal testFileGroup =
804         DataFileGroupInternal.newBuilder()
805             .setGroupName(TEST_GROUP)
806             .setPreserveFilenamesAndIsolateFiles(true)
807             .addFile(relativePathFile)
808             .build();
809 
810     when(mockFileGroupManager.getOnDeviceUris(testFileGroup))
811         .thenReturn(Futures.immediateFuture(ImmutableMap.of(testFileGroup.getFile(0), fileUri1)));
812     when(mockFileGroupManager.getIsolatedFileUris(testFileGroup)).thenReturn(ImmutableMap.of());
813     when(mockFileGroupManager.verifyIsolatedFileUris(any(), any())).thenReturn(ImmutableMap.of());
814 
815     assertThat(
816             mddManager
817                 .getDataFileUri(
818                     relativePathFile, testFileGroup, /* verifyIsolatedStructure= */ true)
819                 .get())
820         .isNull();
821   }
822 
823   @Test
testImportFiles_failed()824   public void testImportFiles_failed() throws Exception {
825     ImmutableList<DataFile> updatedDataFileList =
826         ImmutableList.of(
827             MddTestUtil.createDataFile("inline-file", 0).toBuilder()
828                 .setUrlToDownload("inlinefile:sha1:abcdef")
829                 .build());
830     ImmutableMap<String, FileSource> inlineFileMap =
831         ImmutableMap.of(
832             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
833     when(mockFileGroupManager.importFilesIntoFileGroup(
834             eq(TEST_KEY), anyLong(), any(), any(), any(), any(), any()))
835         .thenReturn(Futures.immediateFailedFuture(new Exception("Test failure")));
836 
837     ExecutionException ex =
838         assertThrows(
839             ExecutionException.class,
840             () ->
841                 mddManager
842                     .importFiles(
843                         TEST_KEY,
844                         1,
845                         "testvariant",
846                         updatedDataFileList,
847                         inlineFileMap,
848                         Optional.absent(),
849                         noCustomValidation())
850                     .get());
851 
852     assertThat(ex).hasMessageThat().contains("Test failure");
853     verify(mockFileGroupManager)
854         .importFilesIntoFileGroup(
855             eq(TEST_KEY),
856             anyLong(),
857             any(),
858             eq(updatedDataFileList),
859             eq(inlineFileMap),
860             any(),
861             any());
862   }
863 
864   @Test
testImportFiles_succeeds()865   public void testImportFiles_succeeds() throws Exception {
866     ImmutableList<DataFile> updatedDataFileList =
867         ImmutableList.of(
868             MddTestUtil.createDataFile("inline-file", 0).toBuilder()
869                 .setUrlToDownload("inlinefile:sha1:abcdef")
870                 .build());
871     ImmutableMap<String, FileSource> inlineFileMap =
872         ImmutableMap.of(
873             "inline-file", FileSource.ofByteString(ByteString.copyFromUtf8("TEST_CONTENT")));
874 
875     when(mockFileGroupManager.importFilesIntoFileGroup(
876             eq(TEST_KEY), anyLong(), any(), any(), any(), any(), any()))
877         .thenReturn(immediateVoidFuture());
878 
879     mddManager
880         .importFiles(
881             TEST_KEY,
882             1,
883             "testvariant",
884             updatedDataFileList,
885             inlineFileMap,
886             Optional.absent(),
887             noCustomValidation())
888         .get();
889 
890     verify(mockFileGroupManager)
891         .importFilesIntoFileGroup(
892             eq(TEST_KEY),
893             anyLong(),
894             any(),
895             eq(updatedDataFileList),
896             eq(inlineFileMap),
897             any(),
898             any());
899   }
900 
901   @Test
testDownloadPendingGroup_failed()902   public void testDownloadPendingGroup_failed() {
903     when(mockFileGroupManager.downloadFileGroup(eq(TEST_KEY), isNull(), any()))
904         .thenReturn(
905             Futures.immediateFailedFuture(
906                 DownloadException.builder()
907                     .setDownloadResultCode(DownloadResultCode.UNKNOWN_ERROR)
908                     .setMessage("Fail")
909                     .build()));
910 
911     ListenableFuture<DataFileGroupInternal> downloadFuture =
912         mddManager.downloadFileGroup(TEST_KEY, Optional.absent(), noCustomValidation());
913     assertThrows(ExecutionException.class, downloadFuture::get);
914     DownloadException unused =
915         LabsFutures.getFailureCauseAs(downloadFuture, DownloadException.class);
916 
917     verify(mockFileGroupManager).downloadFileGroup(eq(TEST_KEY), isNull(), any());
918   }
919 
920   @Test
testDownloadPendingGroup_downloadCondition_absent()921   public void testDownloadPendingGroup_downloadCondition_absent() throws Exception {
922     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
923 
924     when(mockFileGroupManager.downloadFileGroup(eq(TEST_KEY), isNull(), any()))
925         .thenReturn(Futures.immediateFuture(pendingGroup));
926 
927     assertThat(
928             mddManager.downloadFileGroup(TEST_KEY, Optional.absent(), noCustomValidation()).get())
929         .isEqualTo(pendingGroup);
930 
931     verify(mockFileGroupManager).downloadFileGroup(eq(TEST_KEY), isNull(), any());
932   }
933 
934   @Test
testDownloadPendingGroup_downloadCondition_present()935   public void testDownloadPendingGroup_downloadCondition_present() throws Exception {
936     DataFileGroupInternal pendingGroup = MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 1);
937 
938     Optional<DownloadConditions> downloadConditionsOptional =
939         Optional.of(
940             DownloadConditions.newBuilder()
941                 .setDeviceNetworkPolicy(DeviceNetworkPolicy.DOWNLOAD_ON_ANY_NETWORK)
942                 .setDeviceStoragePolicy(DeviceStoragePolicy.BLOCK_DOWNLOAD_IN_LOW_STORAGE)
943                 .build());
944 
945     when(mockFileGroupManager.downloadFileGroup(
946             eq(TEST_KEY), eq(downloadConditionsOptional.get()), any()))
947         .thenReturn(Futures.immediateFuture(pendingGroup));
948 
949     assertThat(
950             mddManager
951                 .downloadFileGroup(TEST_KEY, downloadConditionsOptional, noCustomValidation())
952                 .get())
953         .isEqualTo(pendingGroup);
954 
955     verify(mockFileGroupManager)
956         .downloadFileGroup(eq(TEST_KEY), eq(downloadConditionsOptional.get()), any());
957   }
958 
959   @Test
testDownloadAllPendingGroups()960   public void testDownloadAllPendingGroups() throws Exception {
961     when(mockFileGroupManager.scheduleAllPendingGroupsForDownload(eq(true), any()))
962         .thenReturn(Futures.immediateFuture(null));
963 
964     mddManager.downloadAllPendingGroups(true, noCustomValidation()).get();
965 
966     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
967     verify(mockFileGroupManager).scheduleAllPendingGroupsForDownload(eq(true), any());
968     verifyNoMoreInteractions(mockLogger);
969   }
970 
971   @Test
testVerifyPendingGroups()972   public void testVerifyPendingGroups() throws Exception {
973     when(mockFileGroupManager.verifyAllPendingGroupsDownloaded(any()))
974         .thenReturn(Futures.immediateFuture(null));
975 
976     mddManager.verifyAllPendingGroups(noCustomValidation()).get();
977 
978     verify(mockFileGroupManager).verifyAllPendingGroupsDownloaded(any());
979     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
980     verifyNoMoreInteractions(mockLogger);
981   }
982 
983   @Test
testMaintenance_mddFileExpiration()984   public void testMaintenance_mddFileExpiration() throws Exception {
985     assumeTrue(flags.mddEnableGarbageCollection());
986 
987     setupMaintenanceTasks();
988 
989     mddManager.maintenance().get();
990 
991     verify(mockFileGroupManager).deleteUninstalledAppGroups();
992 
993     verify(mockExpirationHandler).updateExpiration();
994 
995     verify(mockFileGroupStatsLogger).log(DEFAULT_DAYS_SINCE_LAST_LOG);
996     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
997   }
998 
999   @Test
testMaintenance_gcFlagControlsGcDuringMaintenance()1000   public void testMaintenance_gcFlagControlsGcDuringMaintenance() throws Exception {
1001     setupMaintenanceTasks();
1002     flags.mddEnableGarbageCollection = Optional.of(false);
1003 
1004     mddManager.maintenance().get();
1005 
1006     verify(mockExpirationHandler, never()).updateExpiration();
1007   }
1008 
1009   @Test
testMaintenance_logStorage()1010   public void testMaintenance_logStorage() throws Exception {
1011     setupMaintenanceTasks();
1012 
1013     mddManager.maintenance().get();
1014 
1015     verify(mockStorageLogger).logStorageStats(DEFAULT_DAYS_SINCE_LAST_LOG);
1016   }
1017 
1018   @Test
testMaintenance_logNetwork()1019   public void testMaintenance_logNetwork() throws Exception {
1020     setupMaintenanceTasks();
1021 
1022     mddManager.maintenance().get();
1023     verify(mockNetworkLogger).log();
1024   }
1025 
1026   @Test
maintenance_triggerSync_absentSpe()1027   public void maintenance_triggerSync_absentSpe() throws Exception {
1028     mddManager =
1029         new MobileDataDownloadManager(
1030             context,
1031             mockLogger,
1032             mockSharedFileManager,
1033             mockSharedFilesMetadata,
1034             mockFileGroupManager,
1035             mockFileGroupsMetadata,
1036             mockExpirationHandler,
1037             mockSilentFeedback,
1038             mockStorageLogger,
1039             mockFileGroupStatsLogger,
1040             mockNetworkLogger,
1041             Optional.absent(),
1042             CONTROL_EXECUTOR,
1043             flags,
1044             loggingStateStore,
1045             downloadStageManager);
1046 
1047     setupMaintenanceTasks();
1048 
1049     mddManager.maintenance().get();
1050 
1051     // With absent SPE, no triggerSync was called.
1052     verify(mockFileGroupManager, never()).triggerSyncAllPendingGroups();
1053   }
1054 
1055   @Test
testMaintenance_deleteRemovedAccountGroups()1056   public void testMaintenance_deleteRemovedAccountGroups() throws Exception {
1057     setupMaintenanceTasks();
1058 
1059     flags.mddDeleteGroupsRemovedAccounts = Optional.of(true);
1060 
1061     mddManager.maintenance().get();
1062     verify(mockFileGroupManager).deleteRemovedAccountGroups();
1063   }
1064 
setupMaintenanceTasks()1065   void setupMaintenanceTasks() {
1066 
1067     flags.enableDaysSinceLastMaintenanceTracking = Optional.of(true);
1068 
1069     when(mockStorageLogger.logStorageStats(DEFAULT_DAYS_SINCE_LAST_LOG))
1070         .thenReturn(Futures.immediateVoidFuture());
1071     when(mockExpirationHandler.updateExpiration()).thenReturn(Futures.immediateVoidFuture());
1072     when(mockFileGroupStatsLogger.log(DEFAULT_DAYS_SINCE_LAST_LOG))
1073         .thenReturn(Futures.immediateVoidFuture());
1074     when(mockNetworkLogger.log()).thenReturn(Futures.immediateVoidFuture());
1075     when(mockFileGroupManager.logAndDeleteForMissingSharedFiles())
1076         .thenReturn(Futures.immediateVoidFuture());
1077     when(mockFileGroupManager.deleteUninstalledAppGroups())
1078         .thenReturn(Futures.immediateVoidFuture());
1079     when(mockFileGroupManager.deleteRemovedAccountGroups())
1080         .thenReturn(Futures.immediateVoidFuture());
1081     when(mockFileGroupManager.triggerSyncAllPendingGroups()).thenReturn(immediateVoidFuture());
1082 
1083     when(mockFileGroupManager.verifyAndAttemptToRepairIsolatedFiles())
1084         .thenReturn(immediateVoidFuture());
1085   }
1086 
1087   @Test
testRemoveExpiredGroupsAndFiles()1088   public void testRemoveExpiredGroupsAndFiles() throws Exception {
1089     setupMaintenanceTasks();
1090 
1091     mddManager.removeExpiredGroupsAndFiles().get();
1092 
1093     verify(mockExpirationHandler).updateExpiration();
1094   }
1095 
1096   @Test
testClear()1097   public void testClear() throws Exception {
1098     mddManager.clear().get();
1099 
1100     verify(mockSharedFileManager).cancelDownloadAndClear();
1101     verifyNoInteractions(mockLogger);
1102   }
1103 
1104   @Test
testCheckResetTrigger_resetTrigger_noIncrement()1105   public void testCheckResetTrigger_resetTrigger_noIncrement() throws Exception {
1106     setSavedResetValue(1);
1107     flags.mddResetTrigger = Optional.of(1);
1108 
1109     mddManager.checkResetTrigger().get();
1110     verify(mockSharedFileManager, never()).clear();
1111     verifyNoInteractions(mockLogger);
1112     // saved reset value should not have changed
1113     checkSavedResetValue(1);
1114     verifyNoInteractions(mockLogger);
1115   }
1116 
1117   @Test
testCheckResetTrigger_resetTrigger_singleIncrement()1118   public void testCheckResetTrigger_resetTrigger_singleIncrement() throws Exception {
1119     setSavedResetValue(1);
1120     flags.mddResetTrigger = Optional.of(2);
1121 
1122     mddManager.checkResetTrigger().get();
1123     verify(mockSharedFileManager).cancelDownloadAndClear();
1124     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1125     // saved reset value should be set to 2
1126     checkSavedResetValue(2);
1127     verifyNoMoreInteractions(mockLogger);
1128   }
1129 
1130   @Test
testCheckResetTrigger_resetTrigger_singleIncrementMultipleChecks()1131   public void testCheckResetTrigger_resetTrigger_singleIncrementMultipleChecks() throws Exception {
1132     setSavedResetValue(1);
1133     flags.mddResetTrigger = Optional.of(2);
1134 
1135     mddManager.checkResetTrigger().get();
1136     // The second check should have no effect - clear should only be called once.
1137     mddManager.checkResetTrigger().get();
1138     verify(mockSharedFileManager).cancelDownloadAndClear();
1139     verify(mockLogger).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1140     // saved reset value should be set to 2
1141     checkSavedResetValue(2);
1142     verifyNoMoreInteractions(mockLogger);
1143   }
1144 
1145   @Test
testCheckResetTrigger_resetTrigger_multipleIncrementMultipleChecks()1146   public void testCheckResetTrigger_resetTrigger_multipleIncrementMultipleChecks()
1147       throws Exception {
1148     setSavedResetValue(1);
1149     flags.mddResetTrigger = Optional.of(2);
1150 
1151     mddManager.checkResetTrigger().get();
1152 
1153     flags.mddResetTrigger = Optional.of(3);
1154 
1155     mddManager.checkResetTrigger().get();
1156 
1157     verify(mockSharedFileManager, times(2)).cancelDownloadAndClear();
1158     verify(mockLogger, times(2)).logEventSampled(MddClientEvent.Code.EVENT_CODE_UNSPECIFIED);
1159     // saved reset value should be set to 2
1160     checkSavedResetValue(3);
1161     verifyNoMoreInteractions(mockLogger);
1162   }
1163 
1164   @Test
testClear_resetsExperimentIds()1165   public void testClear_resetsExperimentIds() throws Exception {
1166     flags.enableDownloadStageExperimentIdPropagation = Optional.of(true);
1167 
1168     long buildId = 999L;
1169     int experimentId = 12345;
1170     DataFileGroupInternal dataFileGroup =
1171         MddTestUtil.createDataFileGroupInternal(TEST_GROUP, 2).toBuilder()
1172             .setBuildId(buildId)
1173             .build();
1174 
1175     when(mockFileGroupsMetadata.getAllFreshGroups())
1176         .thenReturn(
1177             immediateFuture(
1178                 ImmutableList.of(
1179                     GroupKeyAndGroup.create(
1180                         GroupKey.newBuilder().setGroupName(TEST_GROUP).build(), dataFileGroup))));
1181 
1182     when(mockFileGroupsMetadata.getAllStaleGroups())
1183         .thenReturn(immediateFuture(ImmutableList.of()));
1184 
1185     mddManager.clear().get();
1186 
1187     InOrder inOrder = inOrder(mockFileGroupsMetadata);
1188 
1189     inOrder.verify(mockFileGroupsMetadata).getAllFreshGroups();
1190     inOrder.verify(mockFileGroupsMetadata).clear();
1191   }
1192 
setMigrationState(String key, boolean value)1193   private void setMigrationState(String key, boolean value) {
1194     SharedPreferences sharedPreferences =
1195         SharedPreferencesUtil.getSharedPreferences(
1196             context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
1197     sharedPreferences.edit().putBoolean(key, value).commit();
1198   }
1199 
setSavedResetValue(int value)1200   private void setSavedResetValue(int value) {
1201     SharedPreferences prefs =
1202         SharedPreferencesUtil.getSharedPreferences(
1203             context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
1204     SharedPreferences.Editor editor = prefs.edit();
1205     editor.putInt(MobileDataDownloadManager.RESET_TRIGGER, value);
1206     editor.commit();
1207   }
1208 
checkSavedResetValue(int expected)1209   private void checkSavedResetValue(int expected) {
1210     SharedPreferences prefs =
1211         SharedPreferencesUtil.getSharedPreferences(
1212             context, MobileDataDownloadManager.MDD_MANAGER_METADATA, Optional.absent());
1213     assertThat(prefs.getInt(MobileDataDownloadManager.RESET_TRIGGER, expected - 1))
1214         .isEqualTo(expected);
1215   }
1216 
noCustomValidation()1217   private AsyncFunction<DataFileGroupInternal, Boolean> noCustomValidation() {
1218     return unused -> Futures.immediateFuture(true);
1219   }
1220 }
1221