1 /*
2  * Copyright 2024 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.photopicker.core.network
18 
19 import android.content.Context
20 import android.net.ConnectivityManager
21 import android.net.Network
22 import android.net.NetworkCapabilities
23 import android.net.NetworkRequest
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.SmallTest
26 import com.android.photopicker.util.test.capture
27 import com.android.photopicker.util.test.mockSystemService
28 import com.android.photopicker.util.test.whenever
29 import com.google.common.truth.Truth.assertThat
30 import kotlinx.coroutines.ExperimentalCoroutinesApi
31 import kotlinx.coroutines.flow.first
32 import kotlinx.coroutines.flow.toList
33 import kotlinx.coroutines.launch
34 import kotlinx.coroutines.test.advanceTimeBy
35 import kotlinx.coroutines.test.runTest
36 import org.junit.Before
37 import org.junit.Test
38 import org.junit.runner.RunWith
39 import org.mockito.ArgumentCaptor
40 import org.mockito.Captor
41 import org.mockito.Mock
42 import org.mockito.Mockito.verify
43 import org.mockito.MockitoAnnotations
44 
45 /** Unit tests for the [NetworkManager] */
46 @SmallTest
47 @RunWith(AndroidJUnit4::class)
48 @OptIn(ExperimentalCoroutinesApi::class)
49 class NetworkMonitorTest {
50     lateinit var networkMonitor: NetworkMonitor
51 
52     @Mock lateinit var context: Context
53     @Mock lateinit var mockNetwork: Network
54     @Mock lateinit var mockConnectivityManager: ConnectivityManager
55     @Captor lateinit var callback: ArgumentCaptor<ConnectivityManager.NetworkCallback>
56     @Captor lateinit var networkRequest: ArgumentCaptor<NetworkRequest>
57 
58     @Before
setupnull59     fun setup() {
60         MockitoAnnotations.initMocks(this)
61         mockSystemService(context, ConnectivityManager::class.java) { mockConnectivityManager }
62         whenever(mockConnectivityManager.activeNetwork) { mockNetwork }
63         whenever(mockConnectivityManager.getNetworkCapabilities(mockNetwork)) {
64             NetworkCapabilities.Builder()
65                 .apply { addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) }
66                 .build()
67         }
68     }
69 
70     /** Ensures the initial [NetworkStatus] is emitted before any callbacks are received. */
71     @Test
testInitialNetworkIsAvailablenull72     fun testInitialNetworkIsAvailable() {
73         runTest { // this: TestScope
74             networkMonitor = NetworkMonitor(context, this.backgroundScope)
75             launch {
76                 val reportedStatus = networkMonitor.networkStatus.first()
77                 assertThat(reportedStatus).isEqualTo(NetworkStatus.Available)
78             }
79         }
80     }
81 
82     /** Ensures the [NetworkMonitor] correctly sets up a [ConnectivityManager.NetworkCallback] */
83     @Test
testRegistersNetworkCallbacknull84     fun testRegistersNetworkCallback() {
85         runTest {
86             networkMonitor = NetworkMonitor(context, this.backgroundScope)
87             launch {
88                 val reportedStatus = networkMonitor.networkStatus.first()
89                 assertThat(reportedStatus).isEqualTo(NetworkStatus.Available)
90             }
91             advanceTimeBy(100)
92             verify(mockConnectivityManager)
93                 .registerNetworkCallback(capture(networkRequest), capture(callback))
94 
95             val request: NetworkRequest = networkRequest.getValue()
96             val callback: ConnectivityManager.NetworkCallback = callback.getValue()
97 
98             assertThat(callback).isNotNull()
99             assertThat(request.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue()
100         }
101     }
102 
103     /**
104      * Ensures the [ConnectivityManager.NetworkCallback] emits the correct [NetworkStatus] when
105      * called.
106      */
107     @Test
testCallbackEmitsNewNetworkStatusnull108     fun testCallbackEmitsNewNetworkStatus() {
109         runTest {
110             networkMonitor = NetworkMonitor(context, this.backgroundScope)
111             val emissions = mutableListOf<NetworkStatus>()
112             backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
113             advanceTimeBy(100)
114             verify(mockConnectivityManager)
115                 .registerNetworkCallback(capture(networkRequest), capture(callback))
116 
117             val callback: ConnectivityManager.NetworkCallback = callback.getValue()
118 
119             assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Available)
120 
121             callback.onUnavailable()
122             advanceTimeBy(100)
123 
124             assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Unavailable)
125 
126             callback.onAvailable(mockNetwork)
127             advanceTimeBy(100)
128 
129             assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Available)
130 
131             callback.onLost(mockNetwork)
132             advanceTimeBy(100)
133 
134             assertThat(emissions.removeFirst()).isEqualTo(NetworkStatus.Unavailable)
135         }
136     }
137 
138     /** Ensures new subscribers to the flow only receive the latest [NetworkStatus] */
139     @Test
testNetworkStatusReplayedForNewSubscribersnull140     fun testNetworkStatusReplayedForNewSubscribers() {
141         runTest {
142             networkMonitor = NetworkMonitor(context, this.backgroundScope)
143             val allEmissions = mutableListOf<NetworkStatus>()
144             backgroundScope.launch { networkMonitor.networkStatus.toList(allEmissions) }
145             advanceTimeBy(100)
146             verify(mockConnectivityManager)
147                 .registerNetworkCallback(capture(networkRequest), capture(callback))
148 
149             val callback: ConnectivityManager.NetworkCallback = callback.getValue()
150 
151             callback.onUnavailable()
152             advanceTimeBy(100)
153 
154             callback.onAvailable(mockNetwork)
155             advanceTimeBy(100)
156 
157             callback.onLost(mockNetwork)
158             advanceTimeBy(100)
159 
160             assertThat(allEmissions.size).isEqualTo(4)
161 
162             // Register a new collector, which should jump straight to the end of emissions.
163             val emissions = mutableListOf<NetworkStatus>()
164             backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
165             advanceTimeBy(100)
166 
167             assertThat(emissions.first()).isEqualTo(NetworkStatus.Unavailable)
168             assertThat(emissions.size).isEqualTo(1)
169         }
170     }
171 
172     /** Ensures only new values are emitted from the NetworkStatus flow. */
173     @Test
testNetworkStatusIsDistinctUntilChangednull174     fun testNetworkStatusIsDistinctUntilChanged() {
175         runTest {
176             networkMonitor = NetworkMonitor(context, this.backgroundScope)
177             val emissions = mutableListOf<NetworkStatus>()
178             backgroundScope.launch { networkMonitor.networkStatus.toList(emissions) }
179             advanceTimeBy(100)
180             verify(mockConnectivityManager)
181                 .registerNetworkCallback(capture(networkRequest), capture(callback))
182 
183             val callback: ConnectivityManager.NetworkCallback = callback.getValue()
184 
185             callback.onAvailable(mockNetwork)
186             advanceTimeBy(100)
187 
188             callback.onAvailable(mockNetwork)
189             advanceTimeBy(100)
190 
191             callback.onAvailable(mockNetwork)
192             advanceTimeBy(100)
193 
194             assertThat(emissions.first()).isEqualTo(NetworkStatus.Available)
195             assertThat(emissions.size).isEqualTo(1)
196         }
197     }
198 }
199