/* * Nextcloud Android client application * * @author Chris Narkiewicz * Copyright (C) 2019 Chris Narkiewicz * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.nextcloud.client.network import android.net.ConnectivityManager import android.net.NetworkInfo import com.nextcloud.client.account.Server import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.client.logger.Logger import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.never import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import com.owncloud.android.lib.resources.status.OwnCloudVersion import org.apache.commons.httpclient.HttpClient import org.apache.commons.httpclient.HttpStatus import org.apache.commons.httpclient.methods.GetMethod import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Suite import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.MockitoAnnotations import java.net.URI @RunWith(Suite::class) @Suite.SuiteClasses( ConnectivityServiceTest.IsConnected::class, ConnectivityServiceTest.WifiConnectionWalledStatusOnLegacyServer::class, ConnectivityServiceTest.WifiConnectionWalledStatus::class ) class ConnectivityServiceTest { internal abstract class Base { companion object { fun mockNetworkInfo(connected: Boolean, connecting: Boolean, type: Int): NetworkInfo { val networkInfo = mock() whenever(networkInfo.isConnectedOrConnecting).thenReturn(connected or connecting) whenever(networkInfo.isConnected).thenReturn(connected) whenever(networkInfo.type).thenReturn(type) return networkInfo } const val SERVER_BASE_URL = "https://test.server.com" } @Mock lateinit var platformConnectivityManager: ConnectivityManager @Mock lateinit var networkInfo: NetworkInfo @Mock lateinit var accountManager: UserAccountManager @Mock lateinit var clientFactory: ClientFactory @Mock lateinit var client: HttpClient @Mock lateinit var getRequest: GetMethod @Mock lateinit var requestBuilder: ConnectivityServiceImpl.GetRequestBuilder @Mock lateinit var logger: Logger val baseServerUri = URI.create(SERVER_BASE_URL) val newServer = Server(baseServerUri, OwnCloudVersion.nextcloud_14) val legacyServer = Server(baseServerUri, OwnCloudVersion.nextcloud_13) @Mock lateinit var user: User lateinit var connectivityService: ConnectivityServiceImpl @Before fun setUpMocks() { MockitoAnnotations.initMocks(this) connectivityService = ConnectivityServiceImpl( platformConnectivityManager, accountManager, clientFactory, requestBuilder, logger ) whenever(platformConnectivityManager.activeNetworkInfo).thenReturn(networkInfo) whenever(requestBuilder.invoke(any())).thenReturn(getRequest) whenever(clientFactory.createPlainClient()).thenReturn(client) whenever(user.server).thenReturn(newServer) whenever(accountManager.user).thenReturn(user) } } internal class IsConnected : Base() { @Test fun `connected to wifi`() { whenever(networkInfo.isConnectedOrConnecting).thenReturn(true) whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI) assertTrue(connectivityService.isOnlineWithWifi) } @Test fun `connected to wifi and vpn`() { whenever(networkInfo.isConnectedOrConnecting).thenReturn(true) whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_VPN) val wifiNetworkInfoList = arrayOf( mockNetworkInfo( connected = true, connecting = true, type = ConnectivityManager.TYPE_VPN ), mockNetworkInfo( connected = true, connecting = true, type = ConnectivityManager.TYPE_WIFI ) ) whenever(platformConnectivityManager.allNetworkInfo).thenReturn(wifiNetworkInfoList) assertTrue(connectivityService.isOnlineWithWifi) } @Test fun `connected to mobile network`() { whenever(networkInfo.isConnectedOrConnecting).thenReturn(true) whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_MOBILE) assertFalse(connectivityService.isOnlineWithWifi) } } internal class WifiConnectionWalledStatusOnLegacyServer : Base() { @Before fun setUp() { whenever(networkInfo.isConnectedOrConnecting).thenReturn(true) whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI) whenever(user.server).thenReturn(legacyServer) assertTrue("Precondition failed", connectivityService.isOnlineWithWifi) } fun mockResponse(maintenance: Boolean = true, httpStatus: Int = HttpStatus.SC_OK) { whenever(client.executeMethod(getRequest)).thenReturn(httpStatus) val body = """{"maintenance":$maintenance}""" whenever(getRequest.responseContentLength).thenReturn(body.length.toLong()) whenever(getRequest.responseBodyAsString).thenReturn(body) } @Test fun `false maintenance status flag is used`() { mockResponse(maintenance = false, httpStatus = HttpStatus.SC_OK) assertFalse(connectivityService.isInternetWalled) } @Test fun `true maintenance status flag is used`() { mockResponse(maintenance = true, httpStatus = HttpStatus.SC_OK) assertTrue(connectivityService.isInternetWalled) } @Test fun `maintenance flag is ignored when non-200 HTTP code is returned`() { mockResponse(maintenance = false, httpStatus = HttpStatus.SC_NO_CONTENT) assertTrue(connectivityService.isInternetWalled) } @Test fun `status endpoint is used to determine internet state`() { mockResponse() connectivityService.isInternetWalled val urlCaptor = ArgumentCaptor.forClass(String::class.java) verify(requestBuilder).invoke(urlCaptor.capture()) assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/status.php")) } } internal class WifiConnectionWalledStatus : Base() { @Before fun setUp() { whenever(networkInfo.isConnectedOrConnecting).thenReturn(true) whenever(networkInfo.type).thenReturn(ConnectivityManager.TYPE_WIFI) whenever(accountManager.getServerVersion(any())).thenReturn(OwnCloudVersion.nextcloud_14) assertTrue("Precondition failed", connectivityService.isOnlineWithWifi) } @Test fun `check request is not sent when server uri is not set`() { // GIVEN // network connectivity is present // user has no server URI (empty) val serverWithoutUri = Server(URI(""), OwnCloudVersion.nextcloud_14) whenever(user.server).thenReturn(serverWithoutUri) // WHEN // connectivity is checked val result = connectivityService.isInternetWalled // THEN // connection is walled // request is not sent assertTrue("Server should not be accessible", result) verify(requestBuilder, never()).invoke(any()) verify(client, never()).executeMethod(any()) verify(client, never()).executeMethod(any(), any()) verify(client, never()).executeMethod(any(), any(), any()) } fun mockResponse(contentLength: Long = 0, status: Int = HttpStatus.SC_OK) { whenever(client.executeMethod(any())).thenReturn(status) whenever(getRequest.statusCode).thenReturn(status) whenever(getRequest.responseContentLength).thenReturn(contentLength) } @Test fun `status 204 means internet is not walled`() { mockResponse(contentLength = 0, status = HttpStatus.SC_NO_CONTENT) assertFalse(connectivityService.isInternetWalled) } @Test fun `other status than 204 means internet is walled`() { mockResponse(contentLength = 0, status = HttpStatus.SC_GONE) assertTrue(connectivityService.isInternetWalled) } @Test fun `index endpoint is used to determine internet state`() { mockResponse() connectivityService.isInternetWalled val urlCaptor = ArgumentCaptor.forClass(String::class.java) verify(requestBuilder).invoke(urlCaptor.capture()) assertTrue("Invalid URL used to check status", urlCaptor.value.endsWith("/index.php/204")) } } }