Add integration test for capport API

Add a test to verify the ConnectivityService / NetworkMonitor
integration around the captive portal API.

Test: atest ConnectivityServiceIntegrationTest
Bug: 156062304
Change-Id: I4eed02e09fc4943c011d871c58ba97ec572c7763
This commit is contained in:
Remi NGUYEN VAN
2020-05-10 16:13:15 +09:00
parent 8598423cc4
commit 2ce373abf2
5 changed files with 78 additions and 11 deletions

View File

@@ -30,6 +30,8 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.NETWORK_FACTORY"/>
<!-- Obtain LinkProperties callbacks with sensitive fields -->
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.NETWORK_STACK"/>
<uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY"/>
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>

View File

@@ -28,10 +28,13 @@ import android.net.INetd
import android.net.INetworkPolicyManager
import android.net.INetworkStatsService
import android.net.LinkProperties
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkRequest
import android.net.TestNetworkStackClient
import android.net.Uri
import android.net.metrics.IpConnectivityLog
import android.os.ConditionVariable
import android.os.IBinder
@@ -64,6 +67,8 @@ import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations
import org.mockito.Spy
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
@@ -110,6 +115,10 @@ class ConnectivityServiceIntegrationTest {
private val bindingCondition = ConditionVariable(false)
private val realContext get() = InstrumentationRegistry.getInstrumentation().context
private val httpProbeUrl get() =
realContext.getResources().getString(R.string.config_captive_portal_http_url)
private val httpsProbeUrl get() =
realContext.getResources().getString(R.string.config_captive_portal_https_url)
private class InstrumentationServiceConnection : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
@@ -188,12 +197,8 @@ class ConnectivityServiceIntegrationTest {
val testCallback = TestableNetworkCallback()
cm.registerNetworkCallback(request, testCallback)
nsInstrumentation.addHttpResponse(HttpResponse(
"http://test.android.com",
responseCode = 204, contentLength = 42, redirectUrl = null))
nsInstrumentation.addHttpResponse(HttpResponse(
"https://secure.test.android.com",
responseCode = 204, contentLength = 42, redirectUrl = null))
nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
@@ -204,4 +209,52 @@ class ConnectivityServiceIntegrationTest {
testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
assertEquals(2, nsInstrumentation.getRequestUrls().size)
}
@Test
fun testCapportApi() {
val request = NetworkRequest.Builder()
.clearCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.build()
val testCb = TestableNetworkCallback()
val apiUrl = "https://capport.android.com"
cm.registerNetworkCallback(request, testCb)
nsInstrumentation.addHttpResponse(HttpResponse(
apiUrl,
"""
|{
| "captive": true,
| "user-portal-url": "https://login.capport.android.com",
| "venue-info-url": "https://venueinfo.capport.android.com"
|}
""".trimMargin()))
// Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the
// HTTP probe as it should not be sent.
// Even if the HTTPS probe succeeds, a portal should be detected as the API takes precedence
// in that case.
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
val lp = LinkProperties()
lp.captivePortalApiUrl = Uri.parse(apiUrl)
val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, lp, context)
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
na.addCapability(NET_CAPABILITY_INTERNET)
na.connect()
testCb.expectAvailableCallbacks(na.network, validated = false, tmt = TEST_TIMEOUT_MS)
val capportData = testCb.expectLinkPropertiesThat(na, TEST_TIMEOUT_MS) {
it.captivePortalData != null
}.lp.captivePortalData
assertNotNull(capportData)
assertTrue(capportData.isCaptive)
assertEquals(Uri.parse("https://login.capport.android.com"), capportData.userPortalUrl)
assertEquals(Uri.parse("https://venueinfo.capport.android.com"), capportData.venueInfoUrl)
val nc = testCb.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL, na, TEST_TIMEOUT_MS)
assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED))
}
}

View File

@@ -22,16 +22,21 @@ import android.os.Parcelable
data class HttpResponse(
val requestUrl: String,
val responseCode: Int,
val contentLength: Long,
val redirectUrl: String?
val content: String = "",
val redirectUrl: String? = null
) : Parcelable {
constructor(p: Parcel): this(p.readString(), p.readInt(), p.readLong(), p.readString())
constructor(p: Parcel): this(p.readString(), p.readInt(), p.readString(), p.readString())
constructor(requestUrl: String, contentBody: String): this(
requestUrl,
responseCode = 200,
content = contentBody,
redirectUrl = null)
override fun writeToParcel(dest: Parcel, flags: Int) {
with(dest) {
writeString(requestUrl)
writeInt(responseCode)
writeLong(contentLength)
writeString(content)
writeString(redirectUrl)
}
}

View File

@@ -65,6 +65,9 @@ class NetworkStackInstrumentationService : Service() {
*
* <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
* used to mock the query response.
*
* <p>All requests that are expected to be sent must have a mock response: if an unexpected
* request is seen, the test will fail.
*/
override fun addHttpResponse(response: HttpResponse) {
httpResponses.getValue(response.requestUrl).add(response)

View File

@@ -33,9 +33,11 @@ import com.android.server.net.integrationtests.NetworkStackInstrumentationServic
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLConnection
import java.nio.charset.StandardCharsets
private const val TEST_NETID = 42
@@ -71,11 +73,13 @@ class TestNetworkStackService : Service() {
private inner class TestNetwork(netId: Int) : Network(netId) {
override fun openConnection(url: URL): URLConnection {
val response = InstrumentationConnector.processRequest(url)
val responseBytes = response.content.toByteArray(StandardCharsets.UTF_8)
val connection = mock(HttpURLConnection::class.java)
doReturn(response.responseCode).`when`(connection).responseCode
doReturn(response.contentLength).`when`(connection).contentLengthLong
doReturn(responseBytes.size.toLong()).`when`(connection).contentLengthLong
doReturn(response.redirectUrl).`when`(connection).getHeaderField("location")
doReturn(ByteArrayInputStream(responseBytes)).`when`(connection).inputStream
return connection
}
}