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:
@@ -30,6 +30,8 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.NETWORK_FACTORY"/>
|
<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.NETWORK_STACK"/>
|
||||||
<uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY"/>
|
<uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY"/>
|
||||||
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
|
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
|
||||||
|
|||||||
@@ -28,10 +28,13 @@ import android.net.INetd
|
|||||||
import android.net.INetworkPolicyManager
|
import android.net.INetworkPolicyManager
|
||||||
import android.net.INetworkStatsService
|
import android.net.INetworkStatsService
|
||||||
import android.net.LinkProperties
|
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_INTERNET
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.net.TestNetworkStackClient
|
import android.net.TestNetworkStackClient
|
||||||
|
import android.net.Uri
|
||||||
import android.net.metrics.IpConnectivityLog
|
import android.net.metrics.IpConnectivityLog
|
||||||
import android.os.ConditionVariable
|
import android.os.ConditionVariable
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
@@ -64,6 +67,8 @@ import org.mockito.Mockito.spy
|
|||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
import org.mockito.Spy
|
import org.mockito.Spy
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
@@ -110,6 +115,10 @@ class ConnectivityServiceIntegrationTest {
|
|||||||
private val bindingCondition = ConditionVariable(false)
|
private val bindingCondition = ConditionVariable(false)
|
||||||
|
|
||||||
private val realContext get() = InstrumentationRegistry.getInstrumentation().context
|
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 {
|
private class InstrumentationServiceConnection : ServiceConnection {
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
@@ -188,12 +197,8 @@ class ConnectivityServiceIntegrationTest {
|
|||||||
val testCallback = TestableNetworkCallback()
|
val testCallback = TestableNetworkCallback()
|
||||||
|
|
||||||
cm.registerNetworkCallback(request, testCallback)
|
cm.registerNetworkCallback(request, testCallback)
|
||||||
nsInstrumentation.addHttpResponse(HttpResponse(
|
nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204))
|
||||||
"http://test.android.com",
|
nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204))
|
||||||
responseCode = 204, contentLength = 42, redirectUrl = null))
|
|
||||||
nsInstrumentation.addHttpResponse(HttpResponse(
|
|
||||||
"https://secure.test.android.com",
|
|
||||||
responseCode = 204, contentLength = 42, redirectUrl = null))
|
|
||||||
|
|
||||||
val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
|
val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), context)
|
||||||
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
|
networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS)
|
||||||
@@ -204,4 +209,52 @@ class ConnectivityServiceIntegrationTest {
|
|||||||
testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
|
testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
|
||||||
assertEquals(2, nsInstrumentation.getRequestUrls().size)
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,16 +22,21 @@ import android.os.Parcelable
|
|||||||
data class HttpResponse(
|
data class HttpResponse(
|
||||||
val requestUrl: String,
|
val requestUrl: String,
|
||||||
val responseCode: Int,
|
val responseCode: Int,
|
||||||
val contentLength: Long,
|
val content: String = "",
|
||||||
val redirectUrl: String?
|
val redirectUrl: String? = null
|
||||||
) : Parcelable {
|
) : 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) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
with(dest) {
|
with(dest) {
|
||||||
writeString(requestUrl)
|
writeString(requestUrl)
|
||||||
writeInt(responseCode)
|
writeInt(responseCode)
|
||||||
writeLong(contentLength)
|
writeString(content)
|
||||||
writeString(redirectUrl)
|
writeString(redirectUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ class NetworkStackInstrumentationService : Service() {
|
|||||||
*
|
*
|
||||||
* <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
|
* <p>For any subsequent HTTP/HTTPS query, the first response with a matching URL will be
|
||||||
* used to mock the query response.
|
* 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) {
|
override fun addHttpResponse(response: HttpResponse) {
|
||||||
httpResponses.getValue(response.requestUrl).add(response)
|
httpResponses.getValue(response.requestUrl).add(response)
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ import com.android.server.net.integrationtests.NetworkStackInstrumentationServic
|
|||||||
import org.mockito.Mockito.doReturn
|
import org.mockito.Mockito.doReturn
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
import org.mockito.Mockito.spy
|
import org.mockito.Mockito.spy
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
private const val TEST_NETID = 42
|
private const val TEST_NETID = 42
|
||||||
|
|
||||||
@@ -71,11 +73,13 @@ class TestNetworkStackService : Service() {
|
|||||||
private inner class TestNetwork(netId: Int) : Network(netId) {
|
private inner class TestNetwork(netId: Int) : Network(netId) {
|
||||||
override fun openConnection(url: URL): URLConnection {
|
override fun openConnection(url: URL): URLConnection {
|
||||||
val response = InstrumentationConnector.processRequest(url)
|
val response = InstrumentationConnector.processRequest(url)
|
||||||
|
val responseBytes = response.content.toByteArray(StandardCharsets.UTF_8)
|
||||||
|
|
||||||
val connection = mock(HttpURLConnection::class.java)
|
val connection = mock(HttpURLConnection::class.java)
|
||||||
doReturn(response.responseCode).`when`(connection).responseCode
|
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(response.redirectUrl).`when`(connection).getHeaderField("location")
|
||||||
|
doReturn(ByteArrayInputStream(responseBytes)).`when`(connection).inputStream
|
||||||
return connection
|
return connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user