1 /* 2 * Copyright (C) 2023 The Android Open Source Project 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 17 package com.android.health.connect.backuprestore; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.Mockito.never; 23 import static org.mockito.Mockito.spy; 24 import static org.mockito.Mockito.times; 25 import static org.mockito.Mockito.verify; 26 import static org.mockito.Mockito.when; 27 28 import android.app.backup.BackupAgent; 29 import android.app.backup.BackupDataInput; 30 import android.app.backup.BackupDataOutput; 31 import android.app.backup.FullBackupDataOutput; 32 import android.content.Context; 33 import android.health.connect.HealthConnectManager; 34 import android.health.connect.restore.StageRemoteDataException; 35 import android.os.OutcomeReceiver; 36 import android.os.ParcelFileDescriptor; 37 import android.util.ArrayMap; 38 39 import androidx.test.platform.app.InstrumentationRegistry; 40 import androidx.test.runner.AndroidJUnit4; 41 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.mockito.ArgumentCaptor; 46 import org.mockito.Captor; 47 import org.mockito.Mock; 48 import org.mockito.MockitoAnnotations; 49 50 import java.io.File; 51 import java.io.FileWriter; 52 import java.io.IOException; 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Set; 57 import java.util.stream.Collectors; 58 import java.util.stream.Stream; 59 60 /** Unit test class for {@link HealthConnectBackupAgent} */ 61 @RunWith(AndroidJUnit4.class) 62 public class HealthConnectBackupAgentTest { 63 private TestableHealthConnectBackupAgent mHealthConnectBackupAgent; 64 @Mock private HealthConnectManager mHealthConnectManager; 65 66 @Captor ArgumentCaptor<Map<String, ParcelFileDescriptor>> mPfdsByFileNameCaptor; 67 @Captor ArgumentCaptor<OutcomeReceiver<Void, StageRemoteDataException>> mStagingCallbackCaptor; 68 @Mock private Context mContext; 69 @Mock private FullBackupDataOutput mFullBackupDataOutput; 70 @Mock private ParcelFileDescriptor mOldState; 71 @Mock private ParcelFileDescriptor mNewState; 72 @Mock private BackupDataInput mBackupDataInput; 73 @Mock private BackupDataOutput mBackupDataOutput; 74 private File mBackupDataDirectory; 75 76 @Before setUp()77 public void setUp() { 78 MockitoAnnotations.initMocks(this); 79 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 80 mBackupDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); 81 82 mHealthConnectBackupAgent = new TestableHealthConnectBackupAgent(); 83 mHealthConnectBackupAgent.onCreate(); 84 } 85 86 @Test testOnRestoreFinished_stagingAll_sendsAllDataForStaging()87 public void testOnRestoreFinished_stagingAll_sendsAllDataForStaging() throws IOException { 88 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile1"); 89 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile2"); 90 Set<String> expectedStagedFileNames = 91 Stream.of(mHealthConnectBackupAgent.getBackupDataDir().listFiles()) 92 .filter(file -> !file.isDirectory()) 93 .map(File::getName) 94 .collect(Collectors.toSet()); 95 assertThat(expectedStagedFileNames.size()).isEqualTo(2); 96 97 Set<String> capturedStagedFileNames; 98 99 mHealthConnectBackupAgent.onRestoreFinished(); 100 verify(mHealthConnectManager, times(1)) 101 .stageAllHealthConnectRemoteData(mPfdsByFileNameCaptor.capture(), any(), any()); 102 capturedStagedFileNames = mPfdsByFileNameCaptor.getValue().keySet(); 103 104 assertThat(capturedStagedFileNames.equals(expectedStagedFileNames)).isEqualTo(true); 105 } 106 107 @Test testOnRestoreFinished_afterStagingSuccess_deletesLocalData()108 public void testOnRestoreFinished_afterStagingSuccess_deletesLocalData() throws IOException { 109 HealthConnectBackupAgent spyForVerification = spy(mHealthConnectBackupAgent); 110 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile1"); 111 Set<String> expectedStagedFileNames = 112 Stream.of(mHealthConnectBackupAgent.getBackupDataDir().listFiles()) 113 .filter(file -> !file.isDirectory()) 114 .map(File::getName) 115 .collect(Collectors.toSet()); 116 assertThat(expectedStagedFileNames.size()).isEqualTo(1); 117 118 mHealthConnectBackupAgent.onRestoreFinished(); 119 verify(mHealthConnectManager, times(1)) 120 .stageAllHealthConnectRemoteData(any(), any(), mStagingCallbackCaptor.capture()); 121 mStagingCallbackCaptor.getValue().onResult(null); 122 123 verify(spyForVerification, times(1)).deleteBackupFiles(); 124 } 125 126 @Test testOnRestoreFinished_afterStagingFailure_deletesLocalData()127 public void testOnRestoreFinished_afterStagingFailure_deletesLocalData() throws IOException { 128 HealthConnectBackupAgent spyForVerification = spy(mHealthConnectBackupAgent); 129 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile1"); 130 Set<String> expectedStagedFileNames = 131 Stream.of(mHealthConnectBackupAgent.getBackupDataDir().listFiles()) 132 .filter(file -> !file.isDirectory()) 133 .map(File::getName) 134 .collect(Collectors.toSet()); 135 assertThat(expectedStagedFileNames.size()).isEqualTo(1); 136 137 mHealthConnectBackupAgent.onRestoreFinished(); 138 verify(mHealthConnectManager, times(1)) 139 .stageAllHealthConnectRemoteData(any(), any(), mStagingCallbackCaptor.capture()); 140 mStagingCallbackCaptor.getValue().onError(new StageRemoteDataException(new ArrayMap<>())); 141 142 verify(spyForVerification, times(1)).deleteBackupFiles(); 143 } 144 145 @Test testOnFullBackup_backsUpEverything()146 public void testOnFullBackup_backsUpEverything() throws Exception { 147 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile1"); 148 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile2"); 149 150 mHealthConnectBackupAgent.onFullBackup(mFullBackupDataOutput); 151 152 assertThat(mHealthConnectBackupAgent.mBackedUpFiles).hasSize(2); 153 } 154 155 @Test testOnFullBackup_deviceToDeviceFlag_callsBackupFileNamesWithTrue()156 public void testOnFullBackup_deviceToDeviceFlag_callsBackupFileNamesWithTrue() 157 throws Exception { 158 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile1"); 159 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile2"); 160 161 when(mFullBackupDataOutput.getTransportFlags()) 162 .thenReturn(BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER); 163 mHealthConnectBackupAgent.onFullBackup(mFullBackupDataOutput); 164 165 verify(mHealthConnectManager, times(1)).getAllBackupFileNames(true); 166 } 167 168 @Test testOnFullBackup_noDeviceToDeviceFlag_callsBackupFileNamesWithFalse()169 public void testOnFullBackup_noDeviceToDeviceFlag_callsBackupFileNamesWithFalse() 170 throws Exception { 171 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile1"); 172 createAndGetNonEmptyFile(mBackupDataDirectory, "testFile2"); 173 174 when(mFullBackupDataOutput.getTransportFlags()).thenReturn(0); 175 mHealthConnectBackupAgent.onFullBackup(mFullBackupDataOutput); 176 177 verify(mHealthConnectManager, times(1)).getAllBackupFileNames(false); 178 } 179 180 @Test testOnRestore_doesNotStageAnything()181 public void testOnRestore_doesNotStageAnything() throws IOException { 182 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile1"); 183 createAndGetNonEmptyFile(mHealthConnectBackupAgent.getBackupDataDir(), "testFile2"); 184 Set<String> expectedStagedFileNames = 185 Stream.of(mHealthConnectBackupAgent.getBackupDataDir().listFiles()) 186 .filter(file -> !file.isDirectory()) 187 .map(File::getName) 188 .collect(Collectors.toSet()); 189 assertThat(expectedStagedFileNames.size()).isEqualTo(2); 190 191 mHealthConnectBackupAgent.onRestore(mBackupDataInput, 1, mNewState); 192 verify(mHealthConnectManager, never()) 193 .stageAllHealthConnectRemoteData(mPfdsByFileNameCaptor.capture(), any(), any()); 194 } 195 createAndGetNonEmptyFile(File dir, String fileName)196 private static void createAndGetNonEmptyFile(File dir, String fileName) throws IOException { 197 File file = new File(dir, fileName); 198 FileWriter fileWriter = new FileWriter(file); 199 fileWriter.write("Contents of file " + fileName); 200 fileWriter.close(); 201 } 202 203 /** A testable {@link HealthConnectBackupAgent} */ 204 public class TestableHealthConnectBackupAgent extends HealthConnectBackupAgent { 205 List<File> mBackedUpFiles = new ArrayList<>(); 206 207 @Override getBaseContext()208 public Context getBaseContext() { 209 return mContext; 210 } 211 212 @Override getBackupDataDir()213 File getBackupDataDir() { 214 return mBackupDataDirectory; 215 } 216 217 @Override getHealthConnectService()218 HealthConnectManager getHealthConnectService() { 219 return mHealthConnectManager; 220 } 221 222 @Override backupFile(File file, FullBackupDataOutput data)223 void backupFile(File file, FullBackupDataOutput data) { 224 mBackedUpFiles.add(file); 225 } 226 } 227 } 228