diff --git a/.strict-typing b/.strict-typing index afd8c0fb93e..bd8553359cf 100644 --- a/.strict-typing +++ b/.strict-typing @@ -166,6 +166,7 @@ homeassistant.components.senseme.* homeassistant.components.shelly.* homeassistant.components.simplisafe.* homeassistant.components.slack.* +homeassistant.components.sleepiq.* homeassistant.components.smhi.* homeassistant.components.ssdp.* homeassistant.components.stookalert.* diff --git a/CODEOWNERS b/CODEOWNERS index ceefad2bad4..c0dd609233a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -850,6 +850,8 @@ homeassistant/components/sisyphus/* @jkeljo homeassistant/components/sky_hub/* @rogerselwyn homeassistant/components/slack/* @bachya tests/components/slack/* @bachya +homeassistant/components/sleepiq/* @mfugate1 +tests/components/sleepiq/* @mfugate1 homeassistant/components/slide/* @ualex73 homeassistant/components/sma/* @kellerza @rklomp tests/components/sma/* @kellerza @rklomp diff --git a/homeassistant/components/sleepiq/__init__.py b/homeassistant/components/sleepiq/__init__.py index f6ba5a0393f..5a69cfacd11 100644 --- a/homeassistant/components/sleepiq/__init__.py +++ b/homeassistant/components/sleepiq/__init__.py @@ -1,115 +1,73 @@ """Support for SleepIQ from SleepNumber.""" -from datetime import timedelta import logging from sleepyq import Sleepyq import voluptuous as vol +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import ConfigType -from homeassistant.util import Throttle from .const import DOMAIN - -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) +from .coordinator import SleepIQDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) CONFIG_SCHEMA = vol.Schema( { - vol.Required(DOMAIN): vol.Schema( - { - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - } - ) + DOMAIN: { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } }, extra=vol.ALLOW_EXTRA, ) -def setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the SleepIQ component. +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] - Will automatically load sensor components to support - devices discovered on the account. - """ - username = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - client = Sleepyq(username, password) - try: - data = SleepIQData(client) - data.update() - except ValueError: - message = """ - SleepIQ failed to login, double check your username and password" - """ - _LOGGER.error(message) - return False - hass.data[DOMAIN] = data - discovery.load_platform(hass, Platform.SENSOR, DOMAIN, {}, config) - discovery.load_platform(hass, Platform.BINARY_SENSOR, DOMAIN, {}, config) +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up sleepiq component.""" + if DOMAIN in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN] + ) + ) return True -class SleepIQData: - """Get the latest data from SleepIQ.""" +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up the SleepIQ config entry.""" + client = Sleepyq(entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]) + try: + await hass.async_add_executor_job(client.login) + except ValueError: + _LOGGER.error("SleepIQ login failed, double check your username and password") + return False - def __init__(self, client): - """Initialize the data object.""" - self._client = client - self.beds = {} + coordinator = SleepIQDataUpdateCoordinator( + hass, + client=client, + username=entry.data[CONF_USERNAME], + ) - self.update() + # Call the SleepIQ API to refresh data + await coordinator.async_config_entry_first_refresh() - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self): - """Get the latest data from SleepIQ.""" - self._client.login() - beds = self._client.beds_with_sleeper_status() + hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - self.beds = {bed.bed_id: bed for bed in beds} + hass.config_entries.async_setup_platforms(entry, PLATFORMS) + + return True -class SleepIQSensor(Entity): - """Implementation of a SleepIQ sensor.""" - - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - self._bed_id = bed_id - self._side = side - self.sleepiq_data = sleepiq_data - self.side = None - self.bed = None - - # added by subclass - self._name = None - self.type = None - - @property - def name(self): - """Return the name of the sensor.""" - return "SleepNumber {} {} {}".format( - self.bed.name, self.side.sleeper.first_name, self._name - ) - - @property - def unique_id(self): - """Return a unique ID for the bed.""" - return f"{self._bed_id}-{self._side}-{self.type}" - - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - # Call the API for new sleepiq data. Each sensor will re-trigger this - # same exact call, but that's fine. We cache results for a short period - # of time to prevent hitting API limits. - self.sleepiq_data.update() - - self.bed = self.sleepiq_data.beds[self._bed_id] - self.side = getattr(self.bed, self._side) +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload the config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index f821a569254..890cd6711a6 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,61 +1,49 @@ """Support for SleepIQ sensors.""" -from __future__ import annotations - from homeassistant.components.binary_sensor import ( BinarySensorDeviceClass, BinarySensorEntity, ) -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import SleepIQSensor -from .const import DOMAIN, IS_IN_BED, SENSOR_TYPES, SIDES +from .const import BED, DOMAIN, ICON_EMPTY, ICON_OCCUPIED, IS_IN_BED, SIDES +from .coordinator import SleepIQDataUpdateCoordinator +from .entity import SleepIQSensor -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the SleepIQ sensors.""" - if discovery_info is None: - return - - data = hass.data[DOMAIN] - data.update() - - dev = [] - for bed_id, bed in data.beds.items(): - for side in SIDES: - if getattr(bed, side) is not None: - dev.append(IsInBedBinarySensor(data, bed_id, side)) - add_entities(dev) + """Set up the SleepIQ bed binary sensors.""" + coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + IsInBedBinarySensor(coordinator, bed_id, side) + for side in SIDES + for bed_id in coordinator.data + if getattr(coordinator.data[bed_id][BED], side) is not None + ) class IsInBedBinarySensor(SleepIQSensor, BinarySensorEntity): """Implementation of a SleepIQ presence sensor.""" - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - super().__init__(sleepiq_data, bed_id, side) - self._state = None - self.type = IS_IN_BED - self._name = SENSOR_TYPES[self.type] - self.update() + _attr_device_class = BinarySensorDeviceClass.OCCUPANCY - @property - def is_on(self): - """Return the status of the sensor.""" - return self._state is True + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + ) -> None: + """Initialize the SleepIQ bed side binary sensor.""" + super().__init__(coordinator, bed_id, side, IS_IN_BED) - @property - def device_class(self) -> BinarySensorDeviceClass: - """Return the class of this sensor.""" - return BinarySensorDeviceClass.OCCUPANCY - - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - super().update() - self._state = self.side.is_in_bed + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + super()._async_update_attrs() + self._attr_is_on = getattr(self.side_data, IS_IN_BED) + self._attr_icon = ICON_OCCUPIED if self.is_on else ICON_EMPTY diff --git a/homeassistant/components/sleepiq/config_flow.py b/homeassistant/components/sleepiq/config_flow.py new file mode 100644 index 00000000000..aff4d7e8dc7 --- /dev/null +++ b/homeassistant/components/sleepiq/config_flow.py @@ -0,0 +1,85 @@ +"""Config flow to configure SleepIQ component.""" +from __future__ import annotations + +from typing import Any + +from sleepyq import Sleepyq +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.data_entry_flow import FlowResult + +from .const import DOMAIN, SLEEPYQ_INVALID_CREDENTIALS_MESSAGE + + +class SleepIQFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a SleepIQ config flow.""" + + VERSION = 1 + + async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: + """Import a SleepIQ account as a config entry. + + This flow is triggered by 'async_setup' for configured accounts. + """ + await self.async_set_unique_id(import_config[CONF_USERNAME].lower()) + self._abort_if_unique_id_configured() + + return self.async_create_entry( + title=import_config[CONF_USERNAME], data=import_config + ) + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle a flow initialized by the user.""" + errors = {} + + if user_input is not None: + # Don't allow multiple instances with the same username + await self.async_set_unique_id(user_input[CONF_USERNAME].lower()) + self._abort_if_unique_id_configured() + + login_error = await self.hass.async_add_executor_job( + try_connection, user_input + ) + if not login_error: + return self.async_create_entry( + title=user_input[CONF_USERNAME], data=user_input + ) + + if SLEEPYQ_INVALID_CREDENTIALS_MESSAGE in login_error: + errors["base"] = "invalid_auth" + else: + errors["base"] = "cannot_connect" + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_USERNAME, + default=user_input.get(CONF_USERNAME) + if user_input is not None + else "", + ): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + last_step=True, + ) + + +def try_connection(user_input: dict[str, Any]) -> str: + """Test if the given credentials can successfully login to SleepIQ.""" + + client = Sleepyq(user_input[CONF_USERNAME], user_input[CONF_PASSWORD]) + + try: + client.login() + except ValueError as error: + return str(error) + + return "" diff --git a/homeassistant/components/sleepiq/const.py b/homeassistant/components/sleepiq/const.py index 64f508167e1..3fc0ae999fd 100644 --- a/homeassistant/components/sleepiq/const.py +++ b/homeassistant/components/sleepiq/const.py @@ -1,7 +1,12 @@ """Define constants for the SleepIQ component.""" +DATA_SLEEPIQ = "data_sleepiq" DOMAIN = "sleepiq" +SLEEPYQ_INVALID_CREDENTIALS_MESSAGE = "username or password" +BED = "bed" +ICON_EMPTY = "mdi:bed-empty" +ICON_OCCUPIED = "mdi:bed" IS_IN_BED = "is_in_bed" SLEEP_NUMBER = "sleep_number" SENSOR_TYPES = {SLEEP_NUMBER: "SleepNumber", IS_IN_BED: "Is In Bed"} diff --git a/homeassistant/components/sleepiq/coordinator.py b/homeassistant/components/sleepiq/coordinator.py new file mode 100644 index 00000000000..467238e907e --- /dev/null +++ b/homeassistant/components/sleepiq/coordinator.py @@ -0,0 +1,40 @@ +"""Coordinator for SleepIQ.""" +from datetime import timedelta +import logging + +from sleepyq import Sleepyq + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator + +from .const import BED + +_LOGGER = logging.getLogger(__name__) + +UPDATE_INTERVAL = timedelta(seconds=60) + + +class SleepIQDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict]]): + """SleepIQ data update coordinator.""" + + def __init__( + self, + hass: HomeAssistant, + *, + client: Sleepyq, + username: str, + ) -> None: + """Initialize coordinator.""" + super().__init__( + hass, _LOGGER, name=f"{username}@SleepIQ", update_interval=UPDATE_INTERVAL + ) + self.client = client + + async def _async_update_data(self) -> dict[str, dict]: + return await self.hass.async_add_executor_job(self.update_data) + + def update_data(self) -> dict[str, dict]: + """Get latest data from the client.""" + return { + bed.bed_id: {BED: bed} for bed in self.client.beds_with_sleeper_status() + } diff --git a/homeassistant/components/sleepiq/entity.py b/homeassistant/components/sleepiq/entity.py new file mode 100644 index 00000000000..350435573f1 --- /dev/null +++ b/homeassistant/components/sleepiq/entity.py @@ -0,0 +1,43 @@ +"""Entity for the SleepIQ integration.""" +from homeassistant.core import callback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import BED, ICON_OCCUPIED, SENSOR_TYPES +from .coordinator import SleepIQDataUpdateCoordinator + + +class SleepIQSensor(CoordinatorEntity): + """Implementation of a SleepIQ sensor.""" + + _attr_icon = ICON_OCCUPIED + + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + name: str, + ) -> None: + """Initialize the SleepIQ side entity.""" + super().__init__(coordinator) + self.bed_id = bed_id + self.side = side + + self._async_update_attrs() + + self._attr_name = f"SleepNumber {self.bed_data.name} {self.side_data.sleeper.first_name} {SENSOR_TYPES[name]}" + self._attr_unique_id = ( + f"{self.bed_id}_{self.side_data.sleeper.first_name}_{name}" + ) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self._async_update_attrs() + super()._handle_coordinator_update() + + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + self.bed_data = self.coordinator.data[self.bed_id][BED] + self.side_data = getattr(self.bed_data, self.side) diff --git a/homeassistant/components/sleepiq/manifest.json b/homeassistant/components/sleepiq/manifest.json index ac734393197..a516bd7545b 100644 --- a/homeassistant/components/sleepiq/manifest.json +++ b/homeassistant/components/sleepiq/manifest.json @@ -1,9 +1,13 @@ { "domain": "sleepiq", "name": "SleepIQ", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sleepiq", "requirements": ["sleepyq==0.8.1"], - "codeowners": [], + "codeowners": ["@mfugate1"], + "dhcp": [ + {"macaddress": "64DBA0*"} + ], "iot_class": "cloud_polling", "loggers": ["sleepyq"] } diff --git a/homeassistant/components/sleepiq/sensor.py b/homeassistant/components/sleepiq/sensor.py index 523014cf8cf..52ded76762d 100644 --- a/homeassistant/components/sleepiq/sensor.py +++ b/homeassistant/components/sleepiq/sensor.py @@ -1,62 +1,43 @@ """Support for SleepIQ sensors.""" -from __future__ import annotations - from homeassistant.components.sensor import SensorEntity -from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType -from . import SleepIQSensor -from .const import DOMAIN, SENSOR_TYPES, SIDES, SLEEP_NUMBER - -ICON = "mdi:bed" +from .const import BED, DOMAIN, SIDES, SLEEP_NUMBER +from .coordinator import SleepIQDataUpdateCoordinator +from .entity import SleepIQSensor -def setup_platform( +async def async_setup_entry( hass: HomeAssistant, - config: ConfigType, - add_entities: AddEntitiesCallback, - discovery_info: DiscoveryInfoType | None = None, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, ) -> None: - """Set up the SleepIQ sensors.""" - if discovery_info is None: - return - - data = hass.data[DOMAIN] - data.update() - - dev = [] - for bed_id, bed in data.beds.items(): - for side in SIDES: - if getattr(bed, side) is not None: - dev.append(SleepNumberSensor(data, bed_id, side)) - add_entities(dev) + """Set up the SleepIQ bed sensors.""" + coordinator: SleepIQDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + SleepNumberSensor(coordinator, bed_id, side) + for side in SIDES + for bed_id in coordinator.data + if getattr(coordinator.data[bed_id][BED], side) is not None + ) class SleepNumberSensor(SleepIQSensor, SensorEntity): """Implementation of a SleepIQ sensor.""" - def __init__(self, sleepiq_data, bed_id, side): - """Initialize the sensor.""" - SleepIQSensor.__init__(self, sleepiq_data, bed_id, side) + def __init__( + self, + coordinator: SleepIQDataUpdateCoordinator, + bed_id: str, + side: str, + ) -> None: + """Initialize the SleepIQ sleep number sensor.""" + super().__init__(coordinator, bed_id, side, SLEEP_NUMBER) - self._state = None - self.type = SLEEP_NUMBER - self._name = SENSOR_TYPES[self.type] - - self.update() - - @property - def native_value(self): - """Return the state of the sensor.""" - return self._state - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return ICON - - def update(self): - """Get the latest data from SleepIQ and updates the states.""" - SleepIQSensor.update(self) - self._state = self.side.sleep_number + @callback + def _async_update_attrs(self) -> None: + """Update sensor attributes.""" + super()._async_update_attrs() + self._attr_native_value = self.side_data.sleep_number diff --git a/homeassistant/components/sleepiq/strings.json b/homeassistant/components/sleepiq/strings.json new file mode 100644 index 00000000000..21ceead3d0a --- /dev/null +++ b/homeassistant/components/sleepiq/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" + }, + "step": { + "user": { + "data": { + "password": "[%key:common::config_flow::data::password%]", + "username": "[%key:common::config_flow::data::username%]" + } + } + } + } +} diff --git a/homeassistant/components/sleepiq/translations/en.json b/homeassistant/components/sleepiq/translations/en.json new file mode 100644 index 00000000000..31de29c8690 --- /dev/null +++ b/homeassistant/components/sleepiq/translations/en.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index be18826bedb..72037428e68 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -286,6 +286,7 @@ FLOWS = [ "shopping_list", "sia", "simplisafe", + "sleepiq", "sma", "smappee", "smart_meter_texas", diff --git a/homeassistant/generated/dhcp.py b/homeassistant/generated/dhcp.py index 11c1331a72d..8809dd45f4a 100644 --- a/homeassistant/generated/dhcp.py +++ b/homeassistant/generated/dhcp.py @@ -88,6 +88,7 @@ DHCP: list[dict[str, str | bool]] = [ {'domain': 'senseme', 'macaddress': '20F85E*'}, {'domain': 'sensibo', 'hostname': 'sensibo*'}, {'domain': 'simplisafe', 'hostname': 'simplisafe*', 'macaddress': '30AEA4*'}, + {'domain': 'sleepiq', 'macaddress': '64DBA0*'}, {'domain': 'smartthings', 'hostname': 'st*', 'macaddress': '24FD5B*'}, {'domain': 'smartthings', 'hostname': 'smartthings*', 'macaddress': '24FD5B*'}, {'domain': 'smartthings', 'hostname': 'hub*', 'macaddress': '24FD5B*'}, diff --git a/mypy.ini b/mypy.ini index 18d951cbe30..6187f296ec2 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1635,6 +1635,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.sleepiq.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.smhi.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/sleepiq/conftest.py b/tests/components/sleepiq/conftest.py new file mode 100644 index 00000000000..707fc436c15 --- /dev/null +++ b/tests/components/sleepiq/conftest.py @@ -0,0 +1,75 @@ +"""Common fixtures for sleepiq tests.""" +import json +from unittest.mock import patch + +import pytest +from sleepyq import Bed, FamilyStatus, Sleeper + +from homeassistant.components.sleepiq.const import DOMAIN +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME + +from tests.common import MockConfigEntry, load_fixture + + +def mock_beds(account_type): + """Mock sleepnumber bed data.""" + return [ + Bed(bed) + for bed in json.loads(load_fixture(f"bed{account_type}.json", "sleepiq"))[ + "beds" + ] + ] + + +def mock_sleepers(): + """Mock sleeper data.""" + return [ + Sleeper(sleeper) + for sleeper in json.loads(load_fixture("sleeper.json", "sleepiq"))["sleepers"] + ] + + +def mock_bed_family_status(account_type): + """Mock family status data.""" + return [ + FamilyStatus(status) + for status in json.loads( + load_fixture(f"familystatus{account_type}.json", "sleepiq") + )["beds"] + ] + + +@pytest.fixture +def config_data(): + """Provide configuration data for tests.""" + return { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", + } + + +@pytest.fixture +def config_entry(config_data): + """Create a mock config entry.""" + return MockConfigEntry( + domain=DOMAIN, + data=config_data, + options={}, + ) + + +@pytest.fixture(params=["-single", ""]) +async def setup_entry(hass, request, config_entry): + """Initialize the config entry.""" + with patch("sleepyq.Sleepyq.beds", return_value=mock_beds(request.param)), patch( + "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() + ), patch( + "sleepyq.Sleepyq.bed_family_status", + return_value=mock_bed_family_status(request.param), + ), patch( + "sleepyq.Sleepyq.login" + ): + config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + return {"account_type": request.param, "mock_entry": config_entry} diff --git a/tests/components/sleepiq/fixtures/bed-single.json b/tests/components/sleepiq/fixtures/bed-single.json new file mode 100644 index 00000000000..f1e59f5ad2d --- /dev/null +++ b/tests/components/sleepiq/fixtures/bed-single.json @@ -0,0 +1,27 @@ +{ + "beds" : [ + { + "dualSleep" : false, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "0", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/bed.json b/tests/components/sleepiq/fixtures/bed.json new file mode 100644 index 00000000000..5fb12da0507 --- /dev/null +++ b/tests/components/sleepiq/fixtures/bed.json @@ -0,0 +1,27 @@ +{ + "beds" : [ + { + "dualSleep" : true, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "-92", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] + } diff --git a/tests/components/sleepiq/fixtures/familystatus-single.json b/tests/components/sleepiq/fixtures/familystatus-single.json new file mode 100644 index 00000000000..1d5c0d89943 --- /dev/null +++ b/tests/components/sleepiq/fixtures/familystatus-single.json @@ -0,0 +1,17 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : null + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/familystatus.json b/tests/components/sleepiq/fixtures/familystatus.json new file mode 100644 index 00000000000..c9b60824115 --- /dev/null +++ b/tests/components/sleepiq/fixtures/familystatus.json @@ -0,0 +1,24 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "sleepNumber" : 80, + "alertDetailedMessage" : "No Alert", + "isInBed" : false, + "pressure" : 2191 + } + } + ] + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/login.json b/tests/components/sleepiq/fixtures/login.json new file mode 100644 index 00000000000..a665db7de29 --- /dev/null +++ b/tests/components/sleepiq/fixtures/login.json @@ -0,0 +1,7 @@ +{ + "edpLoginStatus" : 200, + "userId" : "-42", + "registrationState" : 13, + "key" : "0987", + "edpLoginMessage" : "not used" + } \ No newline at end of file diff --git a/tests/components/sleepiq/fixtures/sleeper.json b/tests/components/sleepiq/fixtures/sleeper.json new file mode 100644 index 00000000000..c009e684220 --- /dev/null +++ b/tests/components/sleepiq/fixtures/sleeper.json @@ -0,0 +1,54 @@ +{ + "sleepers" : [ + { + "timezone" : "US/Eastern", + "firstName" : "Test1", + "weight" : 150, + "birthMonth" : 12, + "birthYear" : "1990", + "active" : true, + "lastLogin" : "2016-08-26 21:43:27 CDT", + "side" : 1, + "accountId" : "-32", + "height" : 60, + "bedId" : "-31", + "username" : "test1@example.com", + "sleeperId" : "-80", + "avatar" : "", + "emailValidated" : true, + "licenseVersion" : 6, + "duration" : null, + "email" : "test1@example.com", + "isAccountOwner" : true, + "sleepGoal" : 480, + "zipCode" : "12345", + "isChild" : false, + "isMale" : true + }, + { + "email" : "test2@example.com", + "duration" : null, + "emailValidated" : true, + "licenseVersion" : 5, + "isChild" : false, + "isMale" : false, + "zipCode" : "12345", + "isAccountOwner" : false, + "sleepGoal" : 480, + "side" : 0, + "lastLogin" : "2016-07-17 15:37:30 CDT", + "birthMonth" : 1, + "birthYear" : "1991", + "active" : true, + "weight" : 151, + "firstName" : "Test2", + "timezone" : "US/Eastern", + "avatar" : "", + "username" : "test2@example.com", + "sleeperId" : "-92", + "bedId" : "-31", + "height" : 65, + "accountId" : "-32" + } + ] + } diff --git a/tests/components/sleepiq/test_binary_sensor.py b/tests/components/sleepiq/test_binary_sensor.py index 9b4092d9d48..ca9bf3c84fc 100644 --- a/tests/components/sleepiq/test_binary_sensor.py +++ b/tests/components/sleepiq/test_binary_sensor.py @@ -1,45 +1,34 @@ """The tests for SleepIQ binary sensor platform.""" -from unittest.mock import MagicMock - -from homeassistant.components.sleepiq import binary_sensor as sleepiq -from homeassistant.setup import async_setup_component - -from tests.components.sleepiq.test_init import mock_responses - -CONFIG = {"username": "foo", "password": "bar"} +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er -async def test_sensor_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) +async def test_binary_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors.""" + entity_registry = er.async_get(hass) - await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) + state = hass.states.get("binary_sensor.sleepnumber_ile_test1_is_in_bed") + assert state.state == "on" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 Is In Bed" - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test1_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test1_is_in_bed" - left_side = devices[1] - assert left_side.name == "SleepNumber ILE Test1 Is In Bed" - assert left_side.state == "on" + # If account type is set, only a single bed account was created and there will + # not be a second entity + if setup_entry["account_type"]: + return - right_side = devices[0] - assert right_side.name == "SleepNumber ILE Test2 Is In Bed" - assert right_side.state == "off" + entry = entity_registry.async_get("binary_sensor.sleepnumber_ile_test2_is_in_bed") + assert entry + assert entry.unique_id == "-31_Test2_is_in_bed" - -async def test_setup_single(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock, single=True) - - await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 1 - - right_side = devices[0] - assert right_side.name == "SleepNumber ILE Test1 Is In Bed" - assert right_side.state == "on" + state = hass.states.get("binary_sensor.sleepnumber_ile_test2_is_in_bed") + assert state.state == "off" + assert state.attributes.get(ATTR_ICON) == "mdi:bed-empty" + assert state.attributes.get(ATTR_DEVICE_CLASS) == BinarySensorDeviceClass.OCCUPANCY + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 Is In Bed" diff --git a/tests/components/sleepiq/test_config_flow.py b/tests/components/sleepiq/test_config_flow.py new file mode 100644 index 00000000000..e4a422c888f --- /dev/null +++ b/tests/components/sleepiq/test_config_flow.py @@ -0,0 +1,80 @@ +"""Tests for the SleepIQ config flow.""" +from unittest.mock import patch + +from homeassistant import config_entries, data_entry_flow, setup +from homeassistant.components.sleepiq.const import ( + DOMAIN, + SLEEPYQ_INVALID_CREDENTIALS_MESSAGE, +) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import HomeAssistant + +SLEEPIQ_CONFIG = { + CONF_USERNAME: "username", + CONF_PASSWORD: "password", +} + + +async def test_import(hass: HomeAssistant) -> None: + """Test that we can import a config entry.""" + with patch("sleepyq.Sleepyq.login"): + assert await setup.async_setup_component(hass, DOMAIN, {DOMAIN: SLEEPIQ_CONFIG}) + await hass.async_block_till_done() + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + entry = hass.config_entries.async_entries(DOMAIN)[0] + assert entry.data[CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] + assert entry.data[CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] + + +async def test_show_set_form(hass: HomeAssistant) -> None: + """Test that the setup form is served.""" + with patch("sleepyq.Sleepyq.login"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=None + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + + +async def test_login_invalid_auth(hass: HomeAssistant) -> None: + """Test we show user form with appropriate error on login failure.""" + with patch( + "sleepyq.Sleepyq.login", + side_effect=ValueError(SLEEPYQ_INVALID_CREDENTIALS_MESSAGE), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "invalid_auth"} + + +async def test_login_cannot_connect(hass: HomeAssistant) -> None: + """Test we show user form with appropriate error on login failure.""" + with patch( + "sleepyq.Sleepyq.login", + side_effect=ValueError("Unexpected response code"), + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" + assert result["errors"] == {"base": "cannot_connect"} + + +async def test_success(hass: HomeAssistant) -> None: + """Test successful flow provides entry creation data.""" + with patch("sleepyq.Sleepyq.login"): + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER}, data=SLEEPIQ_CONFIG + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"][CONF_USERNAME] == SLEEPIQ_CONFIG[CONF_USERNAME] + assert result["data"][CONF_PASSWORD] == SLEEPIQ_CONFIG[CONF_PASSWORD] diff --git a/tests/components/sleepiq/test_init.py b/tests/components/sleepiq/test_init.py index 68ca876504f..15af03e14ce 100644 --- a/tests/components/sleepiq/test_init.py +++ b/tests/components/sleepiq/test_init.py @@ -1,65 +1,54 @@ -"""The tests for the SleepIQ component.""" -from http import HTTPStatus -from unittest.mock import MagicMock, patch +"""Tests for the SleepIQ integration.""" +from unittest.mock import patch -from homeassistant import setup -import homeassistant.components.sleepiq as sleepiq +from homeassistant.components.sleepiq.const import DOMAIN +from homeassistant.components.sleepiq.coordinator import UPDATE_INTERVAL +from homeassistant.config_entries import ConfigEntryState +from homeassistant.core import HomeAssistant +from homeassistant.util.dt import utcnow -from tests.common import load_fixture - -CONFIG = {"sleepiq": {"username": "foo", "password": "bar"}} +from tests.common import async_fire_time_changed +from tests.components.sleepiq.conftest import ( + mock_bed_family_status, + mock_beds, + mock_sleepers, +) -def mock_responses(mock, single=False): - """Mock responses for SleepIQ.""" - base_url = "https://prod-api.sleepiq.sleepnumber.com/rest/" - if single: - suffix = "-single" - else: - suffix = "" - mock.put(base_url + "login", text=load_fixture("sleepiq-login.json")) - mock.get(base_url + "bed?_k=0987", text=load_fixture(f"sleepiq-bed{suffix}.json")) - mock.get(base_url + "sleeper?_k=0987", text=load_fixture("sleepiq-sleeper.json")) - mock.get( - base_url + "bed/familyStatus?_k=0987", - text=load_fixture(f"sleepiq-familystatus{suffix}.json"), - ) +async def test_unload_entry(hass: HomeAssistant, setup_entry) -> None: + """Test unloading the SleepIQ entry.""" + entry = setup_entry["mock_entry"] + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state is ConfigEntryState.NOT_LOADED + assert not hass.data.get(DOMAIN) -async def test_setup(hass, requests_mock): - """Test the setup.""" - mock_responses(requests_mock) - - # We're mocking the load_platform discoveries or else the platforms - # will be setup during tear down when blocking till done, but the mocks - # are no longer active. - with patch("homeassistant.helpers.discovery.load_platform", MagicMock()): - assert sleepiq.setup(hass, CONFIG) +async def test_entry_setup_login_error(hass: HomeAssistant, config_entry) -> None: + """Test when sleepyq client is unable to login.""" + with patch("sleepyq.Sleepyq.login", side_effect=ValueError): + config_entry.add_to_hass(hass) + assert not await hass.config_entries.async_setup(config_entry.entry_id) -async def test_setup_login_failed(hass, requests_mock): - """Test the setup if a bad username or password is given.""" - mock_responses(requests_mock) - requests_mock.put( - "https://prod-api.sleepiq.sleepnumber.com/rest/login", - status_code=HTTPStatus.UNAUTHORIZED, - json=load_fixture("sleepiq-login-failed.json"), - ) +async def test_update_interval(hass: HomeAssistant, setup_entry) -> None: + """Test update interval.""" + with patch("sleepyq.Sleepyq.beds", return_value=mock_beds("")) as beds, patch( + "sleepyq.Sleepyq.sleepers", return_value=mock_sleepers() + ) as sleepers, patch( + "sleepyq.Sleepyq.bed_family_status", + return_value=mock_bed_family_status(""), + ) as bed_family_status, patch( + "sleepyq.Sleepyq.login", return_value=True + ): + assert beds.call_count == 0 + assert sleepers.call_count == 0 + assert bed_family_status.call_count == 0 - response = sleepiq.setup(hass, CONFIG) - assert not response + async_fire_time_changed(hass, utcnow() + UPDATE_INTERVAL) + await hass.async_block_till_done() - -async def test_setup_component_no_login(hass): - """Test the setup when no login is configured.""" - conf = CONFIG.copy() - del conf["sleepiq"]["username"] - assert not await setup.async_setup_component(hass, sleepiq.DOMAIN, conf) - - -async def test_setup_component_no_password(hass): - """Test the setup when no password is configured.""" - conf = CONFIG.copy() - del conf["sleepiq"]["password"] - - assert not await setup.async_setup_component(hass, sleepiq.DOMAIN, conf) + assert beds.call_count == 1 + assert sleepers.call_count == 1 + assert bed_family_status.call_count == 1 diff --git a/tests/components/sleepiq/test_sensor.py b/tests/components/sleepiq/test_sensor.py index 7a7e47f03fa..baa8732365b 100644 --- a/tests/components/sleepiq/test_sensor.py +++ b/tests/components/sleepiq/test_sensor.py @@ -1,48 +1,35 @@ """The tests for SleepIQ sensor platform.""" -from unittest.mock import MagicMock - -import homeassistant.components.sleepiq.sensor as sleepiq -from homeassistant.setup import async_setup_component - -from tests.components.sleepiq.test_init import mock_responses - -CONFIG = {"username": "foo", "password": "bar"} +from homeassistant.const import ATTR_FRIENDLY_NAME, ATTR_ICON +from homeassistant.helpers import entity_registry as er -async def test_setup(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock) +async def test_sensors(hass, setup_entry): + """Test the SleepIQ binary sensors for a bed with two sides.""" + entity_registry = er.async_get(hass) - assert await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) + state = hass.states.get("sensor.sleepnumber_ile_test1_sleepnumber") + assert state.state == "40" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test1 SleepNumber" + ) - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 2 + entry = entity_registry.async_get("sensor.sleepnumber_ile_test1_sleepnumber") + assert entry + assert entry.unique_id == "-31_Test1_sleep_number" - left_side = devices[1] - left_side.hass = hass - assert left_side.name == "SleepNumber ILE Test1 SleepNumber" - assert left_side.state == 40 + # If account type is set, only a single bed account was created and there will + # not be a second entity + if setup_entry["account_type"]: + return - right_side = devices[0] - right_side.hass = hass - assert right_side.name == "SleepNumber ILE Test2 SleepNumber" - assert right_side.state == 80 + state = hass.states.get("sensor.sleepnumber_ile_test2_sleepnumber") + assert state.state == "80" + assert state.attributes.get(ATTR_ICON) == "mdi:bed" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) == "SleepNumber ILE Test2 SleepNumber" + ) - -async def test_setup_single(hass, requests_mock): - """Test for successfully setting up the SleepIQ platform.""" - mock_responses(requests_mock, single=True) - - assert await async_setup_component(hass, "sleepiq", {"sleepiq": CONFIG}) - - device_mock = MagicMock() - sleepiq.setup_platform(hass, CONFIG, device_mock, MagicMock()) - devices = device_mock.call_args[0][0] - assert len(devices) == 1 - - right_side = devices[0] - right_side.hass = hass - assert right_side.name == "SleepNumber ILE Test1 SleepNumber" - assert right_side.state == 40 + entry = entity_registry.async_get("sensor.sleepnumber_ile_test2_sleepnumber") + assert entry + assert entry.unique_id == "-31_Test2_sleep_number" diff --git a/tests/fixtures/sleepiq-bed-single.json b/tests/fixtures/sleepiq-bed-single.json deleted file mode 100644 index 512f36c0e6a..00000000000 --- a/tests/fixtures/sleepiq-bed-single.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : false, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "0", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] -} diff --git a/tests/fixtures/sleepiq-bed.json b/tests/fixtures/sleepiq-bed.json deleted file mode 100644 index d03fb6e329f..00000000000 --- a/tests/fixtures/sleepiq-bed.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "beds" : [ - { - "dualSleep" : true, - "base" : "FlexFit", - "sku" : "AILE", - "model" : "ILE", - "size" : "KING", - "isKidsBed" : false, - "sleeperRightId" : "-80", - "accountId" : "-32", - "bedId" : "-31", - "registrationDate" : "2016-07-22T14:00:58Z", - "serial" : null, - "reference" : "95000794555-1", - "macAddress" : "CD13A384BA51", - "version" : null, - "purchaseDate" : "2016-06-22T00:00:00Z", - "sleeperLeftId" : "-92", - "zipcode" : "12345", - "returnRequestStatus" : 0, - "name" : "ILE", - "status" : 1, - "timezone" : "US/Eastern" - } - ] -} - diff --git a/tests/fixtures/sleepiq-familystatus-single.json b/tests/fixtures/sleepiq-familystatus-single.json deleted file mode 100644 index 08c9569c4dc..00000000000 --- a/tests/fixtures/sleepiq-familystatus-single.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : null - } - ] -} diff --git a/tests/fixtures/sleepiq-familystatus.json b/tests/fixtures/sleepiq-familystatus.json deleted file mode 100644 index 0c93d74d35f..00000000000 --- a/tests/fixtures/sleepiq-familystatus.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "beds" : [ - { - "bedId" : "-31", - "rightSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "isInBed" : true, - "sleepNumber" : 40, - "alertDetailedMessage" : "No Alert", - "pressure" : -16 - }, - "status" : 1, - "leftSide" : { - "alertId" : 0, - "lastLink" : "00:00:00", - "sleepNumber" : 80, - "alertDetailedMessage" : "No Alert", - "isInBed" : false, - "pressure" : 2191 - } - } - ] -} diff --git a/tests/fixtures/sleepiq-login-failed.json b/tests/fixtures/sleepiq-login-failed.json deleted file mode 100644 index 227609154b5..00000000000 --- a/tests/fixtures/sleepiq-login-failed.json +++ /dev/null @@ -1 +0,0 @@ -{"Error":{"Code":401,"Message":"Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens."}} diff --git a/tests/fixtures/sleepiq-login.json b/tests/fixtures/sleepiq-login.json deleted file mode 100644 index fdd8943574f..00000000000 --- a/tests/fixtures/sleepiq-login.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "edpLoginStatus" : 200, - "userId" : "-42", - "registrationState" : 13, - "key" : "0987", - "edpLoginMessage" : "not used" -} diff --git a/tests/fixtures/sleepiq-sleeper.json b/tests/fixtures/sleepiq-sleeper.json deleted file mode 100644 index 4089e1b1d95..00000000000 --- a/tests/fixtures/sleepiq-sleeper.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "sleepers" : [ - { - "timezone" : "US/Eastern", - "firstName" : "Test1", - "weight" : 150, - "birthMonth" : 12, - "birthYear" : "1990", - "active" : true, - "lastLogin" : "2016-08-26 21:43:27 CDT", - "side" : 1, - "accountId" : "-32", - "height" : 60, - "bedId" : "-31", - "username" : "test1@example.com", - "sleeperId" : "-80", - "avatar" : "", - "emailValidated" : true, - "licenseVersion" : 6, - "duration" : null, - "email" : "test1@example.com", - "isAccountOwner" : true, - "sleepGoal" : 480, - "zipCode" : "12345", - "isChild" : false, - "isMale" : true - }, - { - "email" : "test2@example.com", - "duration" : null, - "emailValidated" : true, - "licenseVersion" : 5, - "isChild" : false, - "isMale" : false, - "zipCode" : "12345", - "isAccountOwner" : false, - "sleepGoal" : 480, - "side" : 0, - "lastLogin" : "2016-07-17 15:37:30 CDT", - "birthMonth" : 1, - "birthYear" : "1991", - "active" : true, - "weight" : 151, - "firstName" : "Test2", - "timezone" : "US/Eastern", - "avatar" : "", - "username" : "test2@example.com", - "sleeperId" : "-92", - "bedId" : "-31", - "height" : 65, - "accountId" : "-32" - } - ] -} -