ConnectivityServiceTest.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Chris Narkiewicz
  5. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.client.network
  21. import android.net.ConnectivityManager
  22. import android.net.NetworkInfo
  23. import com.nextcloud.client.account.Server
  24. import com.nextcloud.client.account.User
  25. import com.nextcloud.client.account.UserAccountManager
  26. import com.nextcloud.client.logger.Logger
  27. import com.nextcloud.common.PlainClient
  28. import com.nextcloud.operations.GetMethod
  29. import com.nhaarman.mockitokotlin2.any
  30. import com.nhaarman.mockitokotlin2.mock
  31. import com.nhaarman.mockitokotlin2.never
  32. import com.nhaarman.mockitokotlin2.verify
  33. import com.nhaarman.mockitokotlin2.whenever
  34. import com.owncloud.android.lib.resources.status.OwnCloudVersion
  35. import org.apache.commons.httpclient.HttpStatus
  36. import org.junit.Assert.assertFalse
  37. import org.junit.Assert.assertSame
  38. import org.junit.Assert.assertTrue
  39. import org.junit.Before
  40. import org.junit.Test
  41. import org.junit.runner.RunWith
  42. import org.junit.runners.Suite
  43. import org.mockito.ArgumentCaptor
  44. import org.mockito.Mock
  45. import org.mockito.MockitoAnnotations
  46. import java.net.URI
  47. @RunWith(Suite::class)
  48. @Suite.SuiteClasses(
  49. ConnectivityServiceTest.Disconnected::class,
  50. ConnectivityServiceTest.IsConnected::class,
  51. ConnectivityServiceTest.WifiConnectionWalledStatusOnLegacyServer::class,
  52. ConnectivityServiceTest.WifiConnectionWalledStatus::class
  53. )
  54. class ConnectivityServiceTest {
  55. internal abstract class Base {
  56. companion object {
  57. fun mockNetworkInfo(connected: Boolean, connecting: Boolean, type: Int): NetworkInfo {
  58. val networkInfo = mock<NetworkInfo>()
  59. whenever(networkInfo.isConnectedOrConnecting).thenReturn(connected or connecting)
  60. whenever(networkInfo.isConnected).thenReturn(connected)
  61. whenever(networkInfo.type).thenReturn(type)
  62. return networkInfo
  63. }
  64. const val SERVER_BASE_URL = "https://test.server.com"
  65. }
  66. @Mock
  67. lateinit var platformConnectivityManager: ConnectivityManager
  68. @Mock
  69. lateinit var networkInfo: NetworkInfo
  70. @Mock
  71. lateinit var accountManager: UserAccountManager
  72. @Mock
  73. lateinit var clientFactory: ClientFactory
  74. @Mock
  75. lateinit var client: PlainClient
  76. @Mock
  77. lateinit var getRequest: GetMethod
  78. @Mock
  79. lateinit var requestBuilder: ConnectivityServiceImpl.GetRequestBuilder
  80. @Mock
  81. lateinit var logger: Logger
  82. val baseServerUri = URI.create(SERVER_BASE_URL)
  83. val newServer = Server(baseServerUri, OwnCloudVersion.nextcloud_20)
  84. val legacyServer = Server(baseServerUri, OwnCloudVersion.nextcloud_16)
  85. @Mock
  86. lateinit var user: User
  87. lateinit var connectivityService: ConnectivityServiceImpl
  88. @Before
  89. fun setUpMocks() {
  90. MockitoAnnotations.initMocks(this)
  91. connectivityService = ConnectivityServiceImpl(
  92. platformConnectivityManager,
  93. accountManager,
  94. clientFactory,
  95. requestBuilder
  96. )
  97. whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo)
  98. whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
  99. whenever(requestBuilder.invoke(any())).thenReturn(getRequest)
  100. whenever(clientFactory.createPlainClient()).thenReturn(client)
  101. whenever(user.server).thenReturn(newServer)
  102. whenever(accountManager.user).thenReturn(user)
  103. }
  104. }
  105. internal class Disconnected : Base() {
  106. @Test
  107. fun `wifi is disconnected`() {
  108. whenever(networkInfo.isConnectedOrConnecting).thenReturn(false)
  109. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
  110. connectivityService.connectivity.apply {
  111. assertFalse(isConnected)
  112. assertTrue(isWifi)
  113. }
  114. }
  115. @Test
  116. fun `no active network`() {
  117. whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(null)
  118. assertSame(Connectivity.DISCONNECTED, connectivityService.connectivity)
  119. }
  120. }
  121. internal class IsConnected : Base() {
  122. @Test
  123. fun `connected to wifi`() {
  124. whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
  125. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
  126. assertTrue(connectivityService.connectivity.isConnected)
  127. assertTrue(connectivityService.connectivity.isWifi)
  128. }
  129. @Test
  130. fun `connected to wifi and vpn`() {
  131. whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
  132. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_VPN)
  133. val wifiNetworkInfoList = arrayOf(
  134. mockNetworkInfo(
  135. connected = true,
  136. connecting = true,
  137. type = ConnectivityManager.TYPE_VPN
  138. ),
  139. mockNetworkInfo(
  140. connected = true,
  141. connecting = true,
  142. type = ConnectivityManager.TYPE_WIFI
  143. )
  144. )
  145. whenever(platformConnectivityManager.allNetworkInfo).thenReturn(wifiNetworkInfoList)
  146. connectivityService.connectivity.let {
  147. assertTrue(it.isConnected)
  148. assertTrue(it.isWifi)
  149. }
  150. }
  151. @Test
  152. fun `connected to mobile network`() {
  153. whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
  154. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_MOBILE)
  155. whenever(platformConnectivityManager.allNetworkInfo).thenReturn(arrayOf(networkInfo))
  156. connectivityService.connectivity.let {
  157. assertTrue(it.isConnected)
  158. assertFalse(it.isWifi)
  159. }
  160. }
  161. }
  162. internal class WifiConnectionWalledStatusOnLegacyServer : Base() {
  163. @Before
  164. fun setUp() {
  165. whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
  166. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
  167. whenever(user.server).thenReturn(legacyServer)
  168. assertTrue(
  169. "Precondition failed",
  170. connectivityService.connectivity.let {
  171. it.isConnected && it.isWifi
  172. }
  173. )
  174. }
  175. fun mockResponse(maintenance: Boolean = true, httpStatus: Int = HttpStatus.SC_OK) {
  176. whenever(client.execute(getRequest)).thenReturn(httpStatus)
  177. val body =
  178. """{"maintenance":$maintenance}"""
  179. whenever(getRequest.getResponseContentLength()).thenReturn(body.length.toLong())
  180. whenever(getRequest.getResponseBodyAsString()).thenReturn(body)
  181. }
  182. @Test
  183. fun `true maintenance status flag is used`() {
  184. mockResponse(maintenance = true, httpStatus = HttpStatus.SC_OK)
  185. assertTrue(connectivityService.isInternetWalled)
  186. }
  187. @Test
  188. fun `maintenance flag is ignored when non-200 HTTP code is returned`() {
  189. mockResponse(maintenance = false, httpStatus = HttpStatus.SC_NO_CONTENT)
  190. assertTrue(connectivityService.isInternetWalled)
  191. }
  192. @Test
  193. fun `status endpoint is used to determine internet state`() {
  194. mockResponse()
  195. connectivityService.isInternetWalled
  196. val urlCaptor = ArgumentCaptor.forClass(String::class.java)
  197. verify(requestBuilder).invoke(urlCaptor.capture())
  198. assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/204"))
  199. }
  200. }
  201. internal class WifiConnectionWalledStatus : Base() {
  202. @Before
  203. fun setUp() {
  204. whenever(networkInfo.isConnectedOrConnecting).thenReturn(true)
  205. whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI)
  206. whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_20)
  207. assertTrue(
  208. "Precondition failed",
  209. connectivityService.connectivity.let {
  210. it.isConnected && it.isWifi
  211. }
  212. )
  213. }
  214. @Test
  215. fun `check request is not sent when server uri is not set`() {
  216. // GIVEN
  217. // network connectivity is present
  218. // user has no server URI (empty)
  219. val serverWithoutUri = Server(URI(""), OwnCloudVersion.nextcloud_20)
  220. whenever(user.server).thenReturn(serverWithoutUri)
  221. // WHEN
  222. // connectivity is checked
  223. val result = connectivityService.isInternetWalled
  224. // THEN
  225. // connection is walled
  226. // request is not sent
  227. assertTrue("Server should not be accessible", result)
  228. verify(requestBuilder, never()).invoke(any())
  229. verify(client, never()).execute(any())
  230. }
  231. fun mockResponse(contentLength: Long = 0, status: Int = HttpStatus.SC_OK) {
  232. whenever(client.execute(any())).thenReturn(status)
  233. whenever(getRequest.getStatusCode()).thenReturn(status)
  234. whenever(getRequest.getResponseContentLength()).thenReturn(contentLength)
  235. whenever(getRequest.execute(client)).thenReturn(status)
  236. }
  237. @Test
  238. fun `status 204 means internet is not walled`() {
  239. mockResponse(contentLength = 0, status = HttpStatus.SC_NO_CONTENT)
  240. assertFalse(connectivityService.isInternetWalled)
  241. }
  242. @Test
  243. fun `status 204 and no content length means internet is not walled`() {
  244. mockResponse(contentLength = -1, status = HttpStatus.SC_NO_CONTENT)
  245. assertFalse(connectivityService.isInternetWalled)
  246. }
  247. @Test
  248. fun `other status than 204 means internet is walled`() {
  249. mockResponse(contentLength = 0, status = HttpStatus.SC_GONE)
  250. assertTrue(connectivityService.isInternetWalled)
  251. }
  252. @Test
  253. fun `index endpoint is used to determine internet state`() {
  254. mockResponse()
  255. connectivityService.isInternetWalled
  256. val urlCaptor = ArgumentCaptor.forClass(String::class.java)
  257. verify(requestBuilder).invoke(urlCaptor.capture())
  258. assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/index.php/204"))
  259. }
  260. }
  261. }