def test_account(country: str): """Test account functionalities.""" account = DysonAccount() # Login failure with pytest.raises(DysonLoginFailure): account.login_email_password(EMAIL, "wrong_pass", country) assert account.auth_info is None with pytest.raises(DysonAuthRequired): account.devices() # Login succeed account.login_email_password(EMAIL, PASSWORD, country) assert account.auth_info == AUTH_INFO # Devices devices = account.devices() assert devices[0].active is True assert devices[0].serial == DEVICE1_SERIAL assert devices[0].name == DEVICE1_NAME assert devices[0].version == DEVICE1_VERSION assert devices[0].credential == DEVICE1_CREDENTIAL assert devices[0].product_type == DEVICE1_PRODUCT_TYPE assert devices[0].auto_update is True assert devices[0].new_version_available is False assert devices[1].active is None assert devices[1].serial == DEVICE2_SERIAL assert devices[1].name == DEVICE2_NAME assert devices[1].version == DEVICE2_VERSION assert devices[1].credential == DEVICE2_CREDENTIAL assert devices[1].product_type == DEVICE2_PRODUCT_TYPE assert devices[1].auto_update is False assert devices[1].new_version_available is True
def test_login_email_request_too_frequently(mocked_requests: MockedRequests): """Test request for otp code too frequently.""" def _handle_email_request(json: dict, auth: Optional[AuthBase], **kwargs) -> Tuple[int, Optional[dict]]: assert auth is None return (429, None) mocked_requests.register_handler("POST", API_PATH_EMAIL_REQUEST, _handle_email_request) account = DysonAccount() with pytest.raises(DysonOTPTooFrequently): account.login_email_otp(EMAIL, REGION)
async def async_step_email(self, info: Optional[dict] = None): errors = {} if info is not None: email = info[CONF_EMAIL] unique_id = f"global_{email}" for entry in self._async_current_entries(): if entry.unique_id == unique_id: return self.async_abort(reason="already_configured") await self.async_set_unique_id(unique_id) self._abort_if_unique_id_configured() account = DysonAccount() try: self._verify = await self.hass.async_add_executor_job( account.login_email_otp, email, self._region) except DysonNetworkError: errors["base"] = "cannot_connect" except DysonInvalidAccountStatus: errors["base"] = "email_not_registered" else: self._email = email return await self.async_step_email_otp() info = info or {} return self.async_show_form( step_id="email", data_schema=vol.Schema({ vol.Required(CONF_EMAIL, default=info.get(CONF_EMAIL, "")): str, }), errors=errors, )
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Dyson from a config entry.""" # Get devices list if entry.data[CONF_REGION] == "CN": account = DysonAccountCN(entry.data[CONF_AUTH]) else: account = DysonAccount(entry.data[CONF_AUTH]) try: devices = await hass.async_add_executor_job(account.devices) except DysonNetworkError: _LOGGER.error("Cannot connect to Dyson cloud service.") raise ConfigEntryNotReady for device in devices: hass.async_create_task( hass.config_entries.flow.async_init( DYSON_LOCAL_DOMAIN, context={"source": SOURCE_DISCOVERY}, data=device, )) hass.data[DOMAIN][entry.entry_id] = { DATA_ACCOUNT: account, DATA_DEVICES: devices, } for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component)) return True
def test_account(): """Test account functionalities.""" account = DysonAccount() # Incorrect email with pytest.raises(DysonInvalidAccountStatus): account.login_email_otp("*****@*****.**", REGION) assert account.auth_info is None with pytest.raises(DysonAuthRequired): account.devices() # Incorrect password with pytest.raises(DysonLoginFailure): verify = account.login_email_otp(EMAIL, REGION) verify(OTP, "wrong_pass") assert account.auth_info is None # Incorrect OTP with pytest.raises(DysonLoginFailure): verify = account.login_email_otp(EMAIL, REGION) verify("999999", PASSWORD) assert account.auth_info is None # Login succeed verify = account.login_email_otp(EMAIL, REGION) verify(OTP, PASSWORD) assert account.auth_info == AUTH_INFO_BEARER # Devices devices = account.devices() assert devices[0].active is True assert devices[0].serial == DEVICE1_SERIAL assert devices[0].name == DEVICE1_NAME assert devices[0].version == DEVICE1_VERSION assert devices[0].credential == DEVICE1_CREDENTIAL assert devices[0].product_type == DEVICE1_PRODUCT_TYPE assert devices[0].auto_update is True assert devices[0].new_version_available is False assert devices[1].active is None assert devices[1].serial == DEVICE2_SERIAL assert devices[1].name == DEVICE2_NAME assert devices[1].version == DEVICE2_VERSION assert devices[1].credential == DEVICE2_CREDENTIAL assert devices[1].product_type == DEVICE2_PRODUCT_TYPE assert devices[1].auto_update is False assert devices[1].new_version_available is True
def test_server_error(mocked_requests: MockedRequests): """Test cloud server error handling.""" def _handler_network_error(**kwargs): return (500, None) mocked_requests.register_handler("POST", API_PATH_EMAIL_REQUEST, _handler_network_error) mocked_requests.register_handler("GET", API_PATH_DEVICES, _handler_network_error) account = DysonAccount() with pytest.raises(DysonServerError): account.login_email_otp(EMAIL, REGION) account = DysonAccount(AUTH_INFO) with pytest.raises(DysonServerError): account.devices()
def test_network_error(mocked_requests: MockedRequests): """Test network error handling.""" def _handler_network_error(**kwargs): raise requests.RequestException mocked_requests.register_handler("POST", API_PATH_EMAIL_REQUEST, _handler_network_error) mocked_requests.register_handler("GET", API_PATH_DEVICES, _handler_network_error) account = DysonAccount() with pytest.raises(DysonNetworkError): account.login_email_otp(EMAIL, REGION) account = DysonAccount(AUTH_INFO) with pytest.raises(DysonNetworkError): account.devices()
def test_server_error(mocked_requests: MockedRequests, country: str): """Test cloud server error handling.""" def _handler_network_error(**kwargs): return (500, None) mocked_requests.register_handler("POST", API_PATH_LOGIN, _handler_network_error) mocked_requests.register_handler("GET", API_PATH_DEVICES, _handler_network_error) account = DysonAccount() with pytest.raises(DysonServerError): account.login_email_password(EMAIL, PASSWORD, country) account = DysonAccount(AUTH_INFO) with pytest.raises(DysonServerError): account.devices()
def _query_dyson(creds: config.DysonLinkCredentials) -> List[DysonDeviceInfo]: """Queries Dyson's APIs for a device list. This function requires user interaction, to check either their mobile or email for a one-time password. Args: username: email address or mobile number (mobile if country is CN) password: login password country: two-letter country code for account, e.g; IE, CN Returns: list of DysonDeviceInfo """ username = creds.username country = creds.country if country == 'CN': # Treat username like a phone number and use login_mobile_otp. account = DysonAccountCN() if not username.startswith('+86'): username = '******' + username print( f'Please check your mobile device ({username}) for a one-time password.' ) verify_fn = account.login_mobile_otp(username) else: account = DysonAccount() verify_fn = account.login_email_otp(username, country) print(f'Please check your email ({username}) for a one-time password.') print() otp = input('Enter OTP: ') try: verify_fn(otp, creds.password) return account.devices() except DysonLoginFailure: print('Incorrect OTP.') sys.exit(-1)
def test_get_cleaning_map(mocked_requests: MockedRequests, country: str): """Test get cleaning map from the cloud.""" cleaning_id = "edcda2c9-5088-455e-b2ee-9422ef70afb2" cleaning_map = b"mocked_png_image" def _clean_history_handler(auth: Optional[AuthBase], **kwargs) -> Tuple[int, bytes]: assert auth is not None return (0, cleaning_map) mocked_requests.register_handler( "GET", f"/v1/mapvisualizer/devices/{SERIAL}/map/{cleaning_id}", _clean_history_handler, ) account = DysonAccount(AUTH_INFO) device = DysonCloud360Eye(account, SERIAL) assert device.get_cleaning_map(cleaning_id) == cleaning_map # Non existed map assert device.get_cleaning_map("another_id") is None
from libdyson.exceptions import DysonOTPTooFrequently print("Please choose your account region") print("1: Mainland China") print("2: Rest of the World") region = input("Region [1/2]: ") if region == "1": account = DysonAccountCN() mobile = input("Phone number: ") verify = account.login_mobile_otp(f"+86{mobile}") otp = input("Verification code: ") verify(otp) elif region == "2": region = input("Region code: ") account = DysonAccount() email = input("Email: ") password = getpass() account.login_email_password(email, password, region) else: print(f"Invalid input {region}") exit(1) devices = account.devices() for device in devices: print() print(f"Serial: {device.serial}") print(f"Name: {device.name}") print(f"Device Type: {device.product_type}") print(f"Credential: {device.credential}")
def test_account_auth_info(): """Test initialize account with auth info.""" account = DysonAccount(AUTH_INFO) devices = account.devices() assert len(devices) == 2 # Invalid auth account = DysonAccount({ "Account": "invalid", "Password": "******", }, ) with pytest.raises(DysonInvalidAuth): account.devices() # No auth account = DysonAccount() with pytest.raises(DysonAuthRequired): account.devices()
def test_get_cleaning_history(mocked_requests: MockedRequests, country: str): """Test get cleaning history from the cloud.""" cleaning1_id = "edcda2c9-5088-455e-b2ee-9422ef70afb2" cleaning2_id = "98cf2de1-190f-4e68-97b5-c57e7e0604d0" clean_history = { "TriviaMessage": "Your robot has cleaned 1000yds²", "TriviaArea": 800.1243, "Entries": [ { "Clean": cleaning1_id, "Started": "2021-02-10T17:02:00", "Finished": "2021-02-10T17:02:10", "Area": 0.00, "Charges": 0, "Type": "Immediate", "IsInterim": False, }, { "Clean": cleaning2_id, "Started": "2021-02-09T12:10:07", "Finished": "2021-02-09T14:14:11", "Area": 34.70, "Charges": 1, "Type": "Scheduled", "IsInterim": True, }, ], } def _clean_history_handler(auth: Optional[AuthBase], **kwargs) -> Tuple[int, Optional[dict]]: assert auth is not None return (0, clean_history) mocked_requests.register_handler( "GET", f"/v1/assets/devices/{SERIAL}/cleanhistory", _clean_history_handler) account = DysonAccount(AUTH_INFO) device = DysonCloud360Eye(account, SERIAL) cleaning_tasks = device.get_cleaning_history() assert len(cleaning_tasks) == 2 task = cleaning_tasks[0] assert task.cleaning_id == cleaning1_id assert task.start_time == datetime(2021, 2, 10, 17, 2, 0) assert task.finish_time == datetime(2021, 2, 10, 17, 2, 10) assert task.cleaning_time == timedelta(seconds=10) assert task.area == 0.0 assert task.charges == 0 assert task.cleaning_type == CleaningType.Immediate assert task.is_interim is False task = cleaning_tasks[1] assert task.cleaning_id == cleaning2_id assert task.start_time == datetime(2021, 2, 9, 12, 10, 7) assert task.finish_time == datetime(2021, 2, 9, 14, 14, 11) assert task.cleaning_time == timedelta(hours=2, minutes=4, seconds=4) assert task.area == 34.7 assert task.charges == 1 assert task.cleaning_type == CleaningType.Scheduled assert task.is_interim is True