blob: 00a97667c4a12c7cee42385c4484fd036e48f13f [file] [log] [blame]
package com.google.android.connecteddevice.system
import android.bluetooth.BluetoothManager
import android.content.Context
import android.os.Looper
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.companionprotos.SystemQuery
import com.google.android.companionprotos.SystemQueryType.DEVICE_NAME
import com.google.android.companionprotos.SystemQueryType.SYSTEM_QUERY_TYPE_UNKNOWN
import com.google.android.companionprotos.SystemQueryType.USER_ROLE
import com.google.android.companionprotos.SystemUserRole
import com.google.android.companionprotos.SystemUserRoleResponse
import com.google.android.connecteddevice.api.Connector
import com.google.android.connecteddevice.api.FakeConnector
import com.google.android.connecteddevice.model.ConnectedDevice
import com.google.android.connecteddevice.storage.ConnectedDeviceStorage
import com.google.android.connecteddevice.util.ByteUtils
import com.google.common.truth.Truth.assertThat
import com.google.protobuf.ExtensionRegistryLite
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.spy
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import java.nio.charset.StandardCharsets
import java.util.UUID
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows.shadowOf
@RunWith(AndroidJUnit4::class)
class SystemFeatureTest {
private val mockStorage: ConnectedDeviceStorage = mock()
private val fakeConnector = spy(FakeConnector())
private val device =
ConnectedDevice(
UUID.randomUUID().toString(),
/* deviceName= */ null,
/* belongsToDriver= */ true,
/* hasSecureChannel= */ true
)
private val queriedFeature1 = UUID.randomUUID()
private val queriedFeature2 = UUID.randomUUID()
private val onConnectionQueriedFeatures = listOf(queriedFeature1, queriedFeature2)
private lateinit var systemFeature: SystemFeature
@Before
fun setUp() {
val context = ApplicationProvider.getApplicationContext<Context>()
context.getSystemService(BluetoothManager::class.java).adapter.name = TEST_DEVICE_NAME
systemFeature = SystemFeature(context, mockStorage, fakeConnector, onConnectionQueriedFeatures)
assertThat(fakeConnector.callback).isNotNull()
}
@Test
fun start_connectsConnector() {
systemFeature.start()
verify(fakeConnector).connect()
}
@Test
fun stop_disconnectsConnector() {
systemFeature.stop()
verify(fakeConnector).disconnect()
}
@Test
fun onSecureChannelEstablished_sendsQueryForDeviceName() {
fakeConnector.callback?.onSecureChannelEstablished(device)
argumentCaptor<ByteArray>() {
verify(fakeConnector).sendQuerySecurely(eq(device), capture(), anyOrNull(), any())
val systemQuery = SystemQuery.parseFrom(firstValue, ExtensionRegistryLite.getEmptyRegistry())
assertThat(systemQuery.type).isEqualTo(DEVICE_NAME)
}
}
@Test
fun onSecureChannelEstablished_sendsQueryForFeatureSupportStatus() {
runBlocking {
fakeConnector.callback?.onSecureChannelEstablished(device)
shadowOf(Looper.getMainLooper()).idle()
argumentCaptor<List<UUID>>() {
verify(fakeConnector).queryFeatureSupportStatuses(eq(device), capture())
val queriedFeatures = firstValue
assertThat(queriedFeatures).isEqualTo(onConnectionQueriedFeatures)
}
}
}
@Test
fun deviceNameQueryResponse_successUpdatesDeviceName() {
fakeConnector.callback?.onSecureChannelEstablished(device)
val callback =
argumentCaptor<Connector.QueryCallback>().run {
verify(fakeConnector).sendQuerySecurely(eq(device), any(), anyOrNull(), capture())
firstValue
}
callback.onSuccess(TEST_DEVICE_NAME.toByteArray(StandardCharsets.UTF_8))
verify(mockStorage).updateAssociatedDeviceName(device.deviceId, TEST_DEVICE_NAME)
}
@Test
fun deviceNameQueryResponse_successDoesNotUpdateDeviceNameIfEmpty() {
fakeConnector.callback?.onSecureChannelEstablished(device)
argumentCaptor<Connector.QueryCallback>() {
verify(fakeConnector).sendQuerySecurely(eq(device), any(), anyOrNull(), capture())
firstValue.onSuccess(ByteArray(0))
verify(mockStorage, never()).updateAssociatedDeviceName(any(), any())
}
}
@Test
fun deviceNameQueryResponse_onErrorDoesNotUpdateDeviceName() {
fakeConnector.callback?.onSecureChannelEstablished(device)
argumentCaptor<Connector.QueryCallback>() {
verify(fakeConnector).sendQuerySecurely(eq(device), any(), anyOrNull(), capture())
firstValue.onError(ByteArray(0))
verify(mockStorage, never()).updateAssociatedDeviceName(any(), any())
}
}
@Test
fun deviceNameQueryResponse_onQueryFailedToSendDoesNotUpdateDeviceName() {
fakeConnector.callback?.onSecureChannelEstablished(device)
argumentCaptor<Connector.QueryCallback>() {
verify(fakeConnector).sendQuerySecurely(eq(device), any(), anyOrNull(), capture())
firstValue.onQueryFailedToSend(isTransient = false)
firstValue.onQueryFailedToSend(isTransient = true)
verify(mockStorage, never()).updateAssociatedDeviceName(any(), any())
}
}
@Test
fun onQueryReceived_deviceNameQueryRespondsWithName() {
val query = SystemQuery.newBuilder().setType(DEVICE_NAME).build()
val queryId = 0
fakeConnector.callback?.onQueryReceived(device, queryId, query.toByteArray(), null)
argumentCaptor<ByteArray> {
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(true), capture())
assertThat(firstValue).isEqualTo(TEST_DEVICE_NAME.toByteArray(StandardCharsets.UTF_8))
}
}
@Test
fun onQueryReceived_unknownQueryTypeRespondsWithError() {
val query = SystemQuery.newBuilder().setType(SYSTEM_QUERY_TYPE_UNKNOWN).build()
val queryId = 0
fakeConnector.callback?.onQueryReceived(device, queryId, query.toByteArray(), null)
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(false), anyOrNull())
}
@Test
fun onQueryReceived_nullNameFromNameProviderRespondsWithError() {
val context = ApplicationProvider.getApplicationContext<Context>()
context.getSystemService(BluetoothManager::class.java).adapter.name = null
systemFeature = spy(SystemFeature(context, mockStorage, fakeConnector))
val query = SystemQuery.newBuilder().setType(DEVICE_NAME).build()
val queryId = 0
fakeConnector.callback?.onQueryReceived(device, queryId, query.toByteArray(), null)
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(false), anyOrNull())
}
@Test
fun onQueryReceived_failureToParseQueryRespondsWithError() {
val queryId = 0
fakeConnector.callback?.onQueryReceived(device, queryId, ByteUtils.randomBytes(100), null)
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(false), anyOrNull())
}
@Test
fun onQueryReceived_driverDeviceRespondsWithDriver() {
val queryId = 0
val query = SystemQuery.newBuilder().setType(USER_ROLE).build()
val device =
ConnectedDevice(
UUID.randomUUID().toString(),
/* deviceName= */ null,
/* belongsToDriver= */ true,
/* hasSecureChannel= */ true
)
fakeConnector.callback?.onQueryReceived(device, queryId, query.toByteArray(), null)
argumentCaptor<ByteArray> {
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(true), capture())
val response =
SystemUserRoleResponse.parseFrom(firstValue, ExtensionRegistryLite.getEmptyRegistry())
assertThat(response.role).isEqualTo(SystemUserRole.DRIVER)
}
}
@Test
fun onQueryReceived_passengerDeviceRespondsWithPassenger() {
val queryId = 0
val query = SystemQuery.newBuilder().setType(USER_ROLE).build()
val device =
ConnectedDevice(
UUID.randomUUID().toString(),
/* deviceName= */ null,
/* belongsToDriver= */ false,
/* hasSecureChannel= */ true
)
fakeConnector.callback?.onQueryReceived(device, queryId, query.toByteArray(), null)
argumentCaptor<ByteArray> {
verify(fakeConnector).respondToQuerySecurely(eq(device), eq(queryId), eq(true), capture())
val response =
SystemUserRoleResponse.parseFrom(firstValue, ExtensionRegistryLite.getEmptyRegistry())
assertThat(response.role).isEqualTo(SystemUserRole.PASSENGER)
}
}
companion object {
const val TEST_DEVICE_NAME = "TEST_DEVICE_NAME"
}
}