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