1 /**
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * ```
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * ```
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.healthconnect.controller.tests.dataaccess
17 
18 import android.os.Bundle
19 import androidx.lifecycle.MutableLiveData
20 import androidx.test.espresso.Espresso.onView
21 import androidx.test.espresso.action.ViewActions.scrollTo
22 import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
23 import androidx.test.espresso.assertion.ViewAssertions.matches
24 import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
25 import androidx.test.espresso.matcher.ViewMatchers.withId
26 import androidx.test.espresso.matcher.ViewMatchers.withText
27 import com.android.healthconnect.controller.R
28 import com.android.healthconnect.controller.data.access.AccessViewModel
29 import com.android.healthconnect.controller.data.access.AccessViewModel.AccessScreenState
30 import com.android.healthconnect.controller.data.access.AccessViewModel.AccessScreenState.WithData
31 import com.android.healthconnect.controller.data.access.AppAccessMetadata
32 import com.android.healthconnect.controller.data.access.AppAccessState
33 import com.android.healthconnect.controller.dataaccess.HealthDataAccessFragment
34 import com.android.healthconnect.controller.permissions.data.FitnessPermissionType
35 import com.android.healthconnect.controller.permissiontypes.HealthPermissionTypesFragment.Companion.PERMISSION_TYPE_KEY
36 import com.android.healthconnect.controller.shared.app.AppMetadata
37 import com.android.healthconnect.controller.tests.utils.launchFragment
38 import com.android.healthconnect.controller.utils.logging.DataAccessElement
39 import com.android.healthconnect.controller.utils.logging.HealthConnectLogger
40 import com.android.healthconnect.controller.utils.logging.PageName
41 import dagger.hilt.android.testing.BindValue
42 import dagger.hilt.android.testing.HiltAndroidRule
43 import dagger.hilt.android.testing.HiltAndroidTest
44 import org.hamcrest.Matchers.not
45 import org.junit.After
46 import org.junit.Before
47 import org.junit.Rule
48 import org.junit.Test
49 import org.mockito.Mockito
50 import org.mockito.kotlin.atLeast
51 import org.mockito.kotlin.mock
52 import org.mockito.kotlin.reset
53 import org.mockito.kotlin.times
54 import org.mockito.kotlin.verify
55 import org.mockito.kotlin.whenever
56 
57 @HiltAndroidTest
58 class HealthDataAccessFragmentTest {
59 
60     @get:Rule val hiltRule = HiltAndroidRule(this)
61 
62     @BindValue val viewModel: AccessViewModel = Mockito.mock(AccessViewModel::class.java)
63     @BindValue val healthConnectLogger: HealthConnectLogger = mock()
64 
65     @Before
setupnull66     fun setup() {
67         hiltRule.inject()
68     }
69 
70     @After
tearDownnull71     fun tearDown() {
72         reset(healthConnectLogger)
73     }
74 
75     @Test
dataAccessFragment_noSections_noneDisplayednull76     fun dataAccessFragment_noSections_noneDisplayed() {
77         whenever(viewModel.appMetadataMap).then {
78             MutableLiveData<AccessScreenState>(WithData(emptyMap()))
79         }
80         launchFragment<HealthDataAccessFragment>(distanceBundle())
81 
82         onView(withText("Can read distance")).check(doesNotExist())
83         onView(withText("Can write distance")).check(doesNotExist())
84         onView(withText("Inactive apps")).check(doesNotExist())
85         onView(
86                 withText(
87                     "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect"
88                 )
89             )
90             .check(doesNotExist())
91         onView(withText("Manage data")).check(matches(isDisplayed()))
92         onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed()))
93         onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed()))
94     }
95 
96     @Test
dataAccessFragment_readSection_isDisplayednull97     fun dataAccessFragment_readSection_isDisplayed() {
98         val map =
99             mapOf(
100                 AppAccessState.Read to
101                     listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))),
102                 AppAccessState.Write to emptyList(),
103                 AppAccessState.Inactive to emptyList(),
104             )
105         whenever(viewModel.appMetadataMap).then {
106             MutableLiveData<AccessScreenState>(WithData(map))
107         }
108         launchFragment<HealthDataAccessFragment>(distanceBundle())
109 
110         onView(withText("Can read distance")).check(matches(isDisplayed()))
111         onView(withText("Can write distance")).check(doesNotExist())
112         onView(withText("Inactive apps")).check(doesNotExist())
113         onView(
114                 withText(
115                     "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect"
116                 )
117             )
118             .check(doesNotExist())
119         onView(withText("Manage data")).check(matches(isDisplayed()))
120         onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed()))
121         onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed()))
122     }
123 
124     @Test
dataAccessFragment_readAndWriteSections_isDisplayednull125     fun dataAccessFragment_readAndWriteSections_isDisplayed() {
126         val map =
127             mapOf(
128                 AppAccessState.Read to
129                     listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))),
130                 AppAccessState.Write to
131                     listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))),
132                 AppAccessState.Inactive to emptyList(),
133             )
134         whenever(viewModel.appMetadataMap).then {
135             MutableLiveData<AccessScreenState>(WithData(map))
136         }
137         launchFragment<HealthDataAccessFragment>(distanceBundle())
138 
139         onView(withText("Can read distance")).check(matches(isDisplayed()))
140         onView(withText("Can write distance")).check(matches(isDisplayed()))
141         onView(withText("Inactive apps")).check(doesNotExist())
142         onView(
143                 withText(
144                     "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect"
145                 )
146             )
147             .check(doesNotExist())
148         onView(withText("Manage data")).check(matches(isDisplayed()))
149         onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed()))
150         onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed()))
151 
152         verify(healthConnectLogger, atLeast(1)).setPageId(PageName.DATA_ACCESS_PAGE)
153         verify(healthConnectLogger).logPageImpression()
154         verify(healthConnectLogger, times(2))
155             .logImpression(DataAccessElement.DATA_ACCESS_APP_BUTTON)
156         verify(healthConnectLogger).logImpression(DataAccessElement.DELETE_THIS_DATA_BUTTON)
157         verify(healthConnectLogger).logImpression(DataAccessElement.SEE_ALL_ENTRIES_BUTTON)
158     }
159 
160     @Test
dataAccessFragment_inactiveSection_isDisplayednull161     fun dataAccessFragment_inactiveSection_isDisplayed() {
162         val map =
163             mapOf(
164                 AppAccessState.Read to emptyList(),
165                 AppAccessState.Write to emptyList(),
166                 AppAccessState.Inactive to
167                     listOf(AppAccessMetadata(AppMetadata("package1", "appName1", null))),
168             )
169         whenever(viewModel.appMetadataMap).then {
170             MutableLiveData<AccessScreenState>(WithData(map))
171         }
172         launchFragment<HealthDataAccessFragment>(distanceBundle())
173 
174         onView(withText("Can read distance")).check(doesNotExist())
175         onView(withText("Can write distance")).check(doesNotExist())
176         onView(withText("Inactive apps")).check(matches(isDisplayed()))
177         onView(
178                 withText(
179                     "These apps can no longer read or write distance, but still have data stored in Health\u00A0Connect"
180                 )
181             )
182             .check(matches(isDisplayed()))
183         onView(withText("Manage data")).check(matches(isDisplayed()))
184         onView(withText("See all entries")).perform(scrollTo()).check(matches(isDisplayed()))
185         onView(withText("Delete this data")).perform(scrollTo()).check(matches(isDisplayed()))
186         verify(healthConnectLogger).logImpression(DataAccessElement.DATA_ACCESS_INACTIVE_APP_BUTTON)
187     }
188 
189     @Test
dataAccessFragment_loadingState_showsLoadingnull190     fun dataAccessFragment_loadingState_showsLoading() {
191         whenever(viewModel.appMetadataMap).then {
192             MutableLiveData<AccessScreenState>(AccessScreenState.Loading)
193         }
194         launchFragment<HealthDataAccessFragment>(distanceBundle())
195         onView(withId(R.id.progress_indicator)).check(matches(isDisplayed()))
196     }
197 
198     @Test
dataAccessFragment_withData_hidesLoadingnull199     fun dataAccessFragment_withData_hidesLoading() {
200         whenever(viewModel.appMetadataMap).then {
201             MutableLiveData<AccessScreenState>(WithData(emptyMap()))
202         }
203         launchFragment<HealthDataAccessFragment>(distanceBundle())
204         onView(withId(R.id.progress_indicator)).check(matches(not(isDisplayed())))
205     }
206 
207     @Test
dataAccessFragment_withError_showErrornull208     fun dataAccessFragment_withError_showError() {
209         whenever(viewModel.appMetadataMap).then {
210             MutableLiveData<AccessScreenState>(AccessScreenState.Error)
211         }
212         launchFragment<HealthDataAccessFragment>(distanceBundle())
213         onView(withId(R.id.progress_indicator)).check(matches(not(isDisplayed())))
214         onView(withId(R.id.error_view)).check(matches(isDisplayed()))
215     }
216 
distanceBundlenull217     private fun distanceBundle(): Bundle {
218         val bundle = Bundle()
219         bundle.putSerializable(PERMISSION_TYPE_KEY, FitnessPermissionType.DISTANCE)
220         return bundle
221     }
222 }
223