[Local Agent] Add ui automator to interact with snippets Functional proof: https://paste.googleplex.com/6453086699651072 Test coverage: https://paste.googleplex.com/4999773617651712 Design doc: go/ghts:dd:ui-auto Bug: b/282592569 Change-Id: Ifd93ea70bbd2b5be72c4d9f39deb93d2f26e6ec5
diff --git a/local_agent/local_agent.py b/local_agent/local_agent.py index 5400fa1..afae5a8 100644 --- a/local_agent/local_agent.py +++ b/local_agent/local_agent.py
@@ -329,7 +329,6 @@ resp = self._translator.dispatch_to_cmd_handler(rpc_request) elif req_type == RpcRequestType.COMMISSION_TO_GOOGLE_FABRIC: - # TODO(b/282592569): Move commission_to_google_fabric out of translator. resp = self._translator.commission_to_google_fabric(rpc_request) else:
diff --git a/local_agent/tests/unit_tests/test_local_agent.py b/local_agent/tests/unit_tests/test_local_agent.py index 6d05c94..f318c2c 100644 --- a/local_agent/tests/unit_tests/test_local_agent.py +++ b/local_agent/tests/unit_tests/test_local_agent.py
@@ -41,6 +41,9 @@ _FAKE_ERROR_MSG = 'fake-error-msg' _FAKE_COMMISSION_ERROR_MSG = 'Unable to commission the device.' _FAKE_ARTIFACTS_DIR = 'fake-artifacts-dir' +_FAKE_PAIRING_CODE = '34970112332' +_FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name' +_FAKE_GHA_ROOM = 'Office' _START_TEST_SUITE = 'startTestSuite' _END_TEST_SUITE = 'endTestSuite' _COMMISSION_TO_GOOGLE_FABRIC = 'commissionToGoogleFabric' @@ -619,7 +622,12 @@ def test_handle_rpc_request_commission_to_google_fabric_on_success(self, mock_commission): """Verifies handle_rpc_request to commission a device to google fabric on success.""" mock_commission.return_value = _FAKE_RPC_RESPONSE - fake_rpc_request = {'method': _COMMISSION_TO_GOOGLE_FABRIC} + fake_rpc_request = { + 'method': _COMMISSION_TO_GOOGLE_FABRIC, + 'pairingCode': _FAKE_PAIRING_CODE, + 'deviceName': _FAKE_MATTER_DEVICE_NAME, + 'ghaRoom': _FAKE_GHA_ROOM, + } rpc_response = self.proc._handle_rpc_request(fake_rpc_request)
diff --git a/local_agent/tests/unit_tests/test_translation_layer.py b/local_agent/tests/unit_tests/test_translation_layer.py index f292b9c..ce3d204 100644 --- a/local_agent/tests/unit_tests/test_translation_layer.py +++ b/local_agent/tests/unit_tests/test_translation_layer.py
@@ -17,10 +17,12 @@ import unittest from unittest import mock -from local_agent import ams_client from local_agent import errors as agent_errors from local_agent.translation_layer import gdm_manager from local_agent.translation_layer import translation_layer +from mobly.controllers import android_device +from ui_automator import errors as ua_errors +from ui_automator import ui_automator ####################### Fake data for unit test ############################# @@ -28,7 +30,10 @@ _FAKE_DEVICE_TYPE = 'fake-device-type' _FAKE_DEVICE_ID = 'fake-device-id' _FAKE_CAPABILITY = 'fake-device-capability' -_FAKE_PAIRING_CODE = 'fake-pairing-code' +_FAKE_PAIRING_CODE = '34970112332' +_FAKE_MATTER_DEVICE_NAME = 'fake-matter-device-name' +_FAKE_GHA_ROOM = 'Office' +_FAKE_SERIAL = 'fake-serial' _SET_ON = 'setOn' _SET_LOCK = 'setLock' _COMMISSION_TO_GOOGLE_FABRIC = 'commissionToGoogleFabric' @@ -220,21 +225,140 @@ self.mock_client.send_rpc_response.assert_called_once() self.assertTrue(self.translator.is_rpc_timeout(_FAKE_DEVICE_ID)) - def test_commission_to_google_fabric_on_failure(self): - """Verifies commission_to_google_fabric throws an error when pairing code not found.""" + def test_commission_to_google_fabric_raises_an_error_without_pairing_code( + self, + ): + """Verifies commission_to_google_fabric method raises an error when no pairingCode was provided.""" invalid_rpc_request = rpc_request(_COMMISSION_TO_GOOGLE_FABRIC, {}) error_msg = 'Invalid rpc command, no pairingCode in params was found.' + with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg): self.translator.commission_to_google_fabric(invalid_rpc_request) - def test_commission_to_google_fabric_on_success(self): - """Verifies commission_to_google_fabric on success when pairing code is provided.""" - valid_rpc_request = rpc_request( - _COMMISSION_TO_GOOGLE_FABRIC, {'pairingCode': _FAKE_PAIRING_CODE}) - response = self.translator.commission_to_google_fabric(valid_rpc_request) + def test_commission_to_google_fabric_raises_an_error_without_device_name( + self, + ): + """Verifies commission_to_google_fabric method raises an error when no deviceName was provided.""" + invalid_rpc_request = rpc_request( + _COMMISSION_TO_GOOGLE_FABRIC, + { + 'pairingCode': _FAKE_PAIRING_CODE, + }, + ) + error_msg = 'Invalid rpc command, no deviceName in params was found.' - # Id is fake and it was made by `rpc_request` wrapper. - self.assertEqual(response, {'id': 0, 'jsonrpc': '2.0', 'result': {}}) + with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg): + self.translator.commission_to_google_fabric(invalid_rpc_request) + + def test_commission_to_google_fabric_raises_an_error_without_gha_room(self): + """Verifies commission_to_google_fabric method raises an error when no ghaRoom was provided.""" + invalid_rpc_request = rpc_request( + _COMMISSION_TO_GOOGLE_FABRIC, + { + 'pairingCode': _FAKE_PAIRING_CODE, + 'deviceName': _FAKE_MATTER_DEVICE_NAME, + }, + ) + error_msg = 'Invalid rpc command, no ghaRoom in params was found.' + + with self.assertRaisesRegex(agent_errors.InvalidRPCError, error_msg): + self.translator.commission_to_google_fabric(invalid_rpc_request) + + @mock.patch.object(android_device, 'AndroidDevice') + @mock.patch.object(android_device, 'get_all_instances') + @mock.patch.object(translation_layer, 'logger') + @mock.patch.object(ui_automator.UIAutomator, 'commission_device') + def test_commission_to_google_fabric_raises_an_error_when_commissioning_fails( + self, + mock_commission_device, + mock_logger, + mock_get_all_instances, + mock_android_device, + ): + """Verifies commission_to_google_fabric method raises an error when running commissioning method in snippet apk fails.""" + mock_get_all_instances.return_value = [ + android_device.AndroidDevice(_FAKE_SERIAL) + ] + expected_error_message = ( + 'Unable to continue automated commissioning process on' + ' device(fake-serial).' + ) + mock_commission_device.side_effect = ua_errors.MoblySnippetError( + expected_error_message + ) + + response = self.translator.commission_to_google_fabric( + rpc_request( + _COMMISSION_TO_GOOGLE_FABRIC, + { + 'pairingCode': _FAKE_PAIRING_CODE, + 'deviceName': _FAKE_MATTER_DEVICE_NAME, + 'ghaRoom': _FAKE_GHA_ROOM, + }, + ) + ) + + self.assertEqual( + response, + { + 'jsonrpc': '2.0', + 'id': 0, + 'result': { + 'isCommissioned': False, + 'errorLog': expected_error_message, + }, + }, + ) + mock_commission_device.assert_called_once_with( + pairing_code=_FAKE_PAIRING_CODE, + device_name=_FAKE_MATTER_DEVICE_NAME, + gha_room=_FAKE_GHA_ROOM, + ) + mock_logger.info.assert_called_once_with( + f'Completed request for 0, pairingCode: {_FAKE_PAIRING_CODE},' + f' deviceName: {_FAKE_MATTER_DEVICE_NAME}, ghaRoom: {_FAKE_GHA_ROOM}' + ) + + @mock.patch.object(android_device, 'AndroidDevice') + @mock.patch.object(android_device, 'get_all_instances') + @mock.patch.object(translation_layer, 'logger') + @mock.patch.object(ui_automator.UIAutomator, 'commission_device') + def test_commission_to_google_fabric_on_success( + self, + mock_commission_device, + mock_logger, + mock_get_all_instances, + mock_android_device, + ): + """Verifies commission_to_google_fabric on success when required params are provided.""" + mock_get_all_instances.return_value = [ + android_device.AndroidDevice(_FAKE_SERIAL) + ] + + response = self.translator.commission_to_google_fabric( + rpc_request( + _COMMISSION_TO_GOOGLE_FABRIC, + { + 'pairingCode': _FAKE_PAIRING_CODE, + 'deviceName': _FAKE_MATTER_DEVICE_NAME, + 'ghaRoom': _FAKE_GHA_ROOM, + }, + ) + ) + + mock_commission_device.assert_called_once_with( + pairing_code=_FAKE_PAIRING_CODE, + device_name=_FAKE_MATTER_DEVICE_NAME, + gha_room=_FAKE_GHA_ROOM, + ) + mock_logger.info.assert_called_once_with( + f'Completed request for 0, pairingCode: {_FAKE_PAIRING_CODE},' + f' deviceName: {_FAKE_MATTER_DEVICE_NAME}, ghaRoom: {_FAKE_GHA_ROOM}' + ) + self.assertEqual( + response, + {'jsonrpc': '2.0', 'id': 0, 'result': {'isCommissioned': True}}, + ) if __name__ == '__main__': unittest.main(failfast=True)
diff --git a/local_agent/translation_layer/translation_layer.py b/local_agent/translation_layer/translation_layer.py index 5da05fe..b652926 100644 --- a/local_agent/translation_layer/translation_layer.py +++ b/local_agent/translation_layer/translation_layer.py
@@ -25,7 +25,8 @@ from local_agent.translation_layer import gdm_manager from local_agent.translation_layer.command_handlers import base from local_agent.translation_layer.command_handlers.handler_registry import GDM_CAPABILITIES_TO_COMMAND_HANDLERS - +from ui_automator import errors as ua_errors +from ui_automator import ui_automator logger = logger_module.get_logger() @@ -77,6 +78,9 @@ # GDM manager self._mgr = gdm_manager.GdmManager(self.update_handlers_cls_map) + # UI Automator + self._ui_automator = ui_automator.UIAutomator() + # Checks busy devices self._rpc_execution_lock = threading.RLock() self._busy_devices = set() @@ -296,10 +300,40 @@ pairing_code = rpc_request['params'].get('pairingCode') if pairing_code is None: raise agent_errors.InvalidRPCError( - 'Invalid rpc command, no pairingCode in params was found.') + 'Invalid rpc command, no pairingCode in params was found.' + ) + + device_name = rpc_request['params'].get('deviceName') + if device_name is None: + raise agent_errors.InvalidRPCError( + 'Invalid rpc command, no deviceName in params was found.' + ) + + gha_room = rpc_request['params'].get('ghaRoom') + if gha_room is None: + raise agent_errors.InvalidRPCError( + 'Invalid rpc command, no ghaRoom in params was found.' + ) request_id = rpc_request['id'] - # TODO(b/282592569): Use this pairing code to commission a matter device on GHA. - logger.info(f'Completed request for {request_id}, pairingCode: {pairing_code}') + try: + self._ui_automator.commission_device( + device_name=device_name, pairing_code=pairing_code, gha_room=gha_room + ) + except (ua_errors.AndroidDeviceNotReadyError, ua_errors.MoblySnippetError) as e: + return { + 'id': request_id, + 'jsonrpc': '2.0', + 'result': {'isCommissioned': False, 'errorLog': str(e)}, + } + finally: + logger.info( + f'Completed request for {request_id}, pairingCode: {pairing_code},' + f' deviceName: {device_name}, ghaRoom: {gha_room}' + ) - return {'id': request_id, 'jsonrpc': '2.0', 'result': {}} + return { + 'id': request_id, + 'jsonrpc': '2.0', + 'result': {'isCommissioned': True}, + }