1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 00:20:30 +01:00

Clean up SmartTub integration and tests (#165517)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Matt Zimmerman
2026-03-16 14:06:23 -07:00
committed by GitHub
parent 98a9ce3a64
commit 1817522107
6 changed files with 151 additions and 121 deletions

View File

@@ -19,8 +19,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: SmartTubConfigEntry) ->
controller = SmartTubController(hass)
if not await controller.async_setup_entry(entry):
return False
await controller.async_setup_entry(entry)
entry.runtime_data = controller

View File

@@ -21,12 +21,13 @@ def config_data() -> dict[str, Any]:
@pytest.fixture
def config_entry(config_data: dict[str, Any]) -> MockConfigEntry:
def config_entry(config_data: dict[str, Any], account) -> MockConfigEntry:
"""Create a mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
data=config_data,
options={},
unique_id=account.id,
)

View File

@@ -5,7 +5,6 @@ import smarttub
from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE,
ATTR_HVAC_ACTION,
ATTR_HVAC_MODE,
ATTR_HVAC_MODES,
ATTR_MAX_TEMP,
ATTR_MIN_TEMP,
@@ -14,7 +13,6 @@ from homeassistant.components.climate import (
DOMAIN as CLIMATE_DOMAIN,
PRESET_ECO,
PRESET_NONE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
ClimateEntityFeature,
@@ -32,25 +30,16 @@ from homeassistant.core import HomeAssistant
from . import trigger_update
async def test_thermostat_update(
async def test_thermostat_state(
spa, spa_state, setup_entry, hass: HomeAssistant
) -> None:
"""Test the thermostat entity."""
"""Test the thermostat entity initial state and attributes."""
entity_id = f"climate.{spa.brand}_{spa.model}_thermostat"
state = hass.states.get(entity_id)
assert state
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
spa_state.heater = "OFF"
await trigger_update(hass)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
assert set(state.attributes[ATTR_HVAC_MODES]) == {HVACMode.HEAT}
assert state.state == HVACMode.HEAT
assert set(state.attributes[ATTR_HVAC_MODES]) == {HVACMode.HEAT}
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
assert (
state.attributes[ATTR_SUPPORTED_FEATURES]
== ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
@@ -60,7 +49,28 @@ async def test_thermostat_update(
assert state.attributes[ATTR_MAX_TEMP] == DEFAULT_MAX_TEMP
assert state.attributes[ATTR_MIN_TEMP] == DEFAULT_MIN_TEMP
assert state.attributes[ATTR_PRESET_MODES] == ["none", "eco", "day", "ready"]
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE
async def test_thermostat_hvac_action_update(
spa, spa_state, setup_entry, hass: HomeAssistant
) -> None:
"""Test the thermostat HVAC action transitions from heating to idle."""
entity_id = f"climate.{spa.brand}_{spa.model}_thermostat"
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
spa_state.heater = "OFF"
await trigger_update(hass)
state = hass.states.get(entity_id)
assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
async def test_thermostat_set_temperature(
spa, setup_entry, hass: HomeAssistant
) -> None:
"""Test setting the target temperature."""
entity_id = f"climate.{spa.brand}_{spa.model}_thermostat"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
@@ -69,15 +79,12 @@ async def test_thermostat_update(
)
spa.set_temperature.assert_called_with(37)
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVACMode.HEAT},
blocking=True,
)
# does nothing
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_NONE
async def test_thermostat_set_preset_mode(
spa, spa_state, setup_entry, hass: HomeAssistant
) -> None:
"""Test setting a preset mode updates state correctly."""
entity_id = f"climate.{spa.brand}_{spa.model}_thermostat"
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
@@ -91,6 +98,9 @@ async def test_thermostat_update(
state = hass.states.get(entity_id)
assert state.attributes.get(ATTR_PRESET_MODE) == PRESET_ECO
async def test_thermostat_api_error(spa, setup_entry, hass: HomeAssistant) -> None:
"""Test that an API error during update does not raise."""
spa.get_status_full.side_effect = smarttub.APIError
await trigger_update(hass)
# should not fail

View File

@@ -2,6 +2,7 @@
from unittest.mock import patch
import pytest
from smarttub import LoginFailed
from homeassistant import config_entries
@@ -13,35 +14,43 @@ from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form."""
@pytest.fixture
def mock_setup_entry():
"""Mock the integration setup."""
with patch(
"homeassistant.components.smarttub.async_setup_entry",
return_value=True,
) as mock:
yield mock
async def test_user_flow(hass: HomeAssistant, mock_setup_entry, account) -> None:
"""Test the user config flow creates an entry with correct data."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.smarttub.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_EMAIL: "test-email", CONF_PASSWORD: "test-password"},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_EMAIL: "test-email", CONF_PASSWORD: "test-password"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-email"
assert result["data"] == {
CONF_EMAIL: "test-email",
CONF_PASSWORD: "test-password",
}
await hass.async_block_till_done()
mock_setup_entry.assert_called_once()
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test-email"
assert result["data"] == {
CONF_EMAIL: "test-email",
CONF_PASSWORD: "test-password",
}
assert result["result"].unique_id == account.id
mock_setup_entry.assert_called_once()
async def test_form_invalid_auth(hass: HomeAssistant, smarttub_api) -> None:
"""Test we handle invalid auth."""
async def test_form_invalid_auth(
hass: HomeAssistant, smarttub_api, mock_setup_entry
) -> None:
"""Test we handle invalid auth and can recover."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
@@ -56,17 +65,21 @@ async def test_form_invalid_auth(hass: HomeAssistant, smarttub_api) -> None:
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
smarttub_api.login.side_effect = None
async def test_reauth_success(hass: HomeAssistant, smarttub_api, account) -> None:
"""Test reauthentication flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_EMAIL: "test-email", CONF_PASSWORD: "test-password"},
unique_id=account.id,
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_EMAIL: "test-email", CONF_PASSWORD: "test-password"},
)
mock_entry.add_to_hass(hass)
result = await mock_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_reauth_success(hass: HomeAssistant, smarttub_api, config_entry) -> None:
"""Test reauthentication flow."""
config_entry.add_to_hass(hass)
result = await config_entry.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reauth_confirm"
@@ -77,18 +90,15 @@ async def test_reauth_success(hass: HomeAssistant, smarttub_api, account) -> Non
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
assert mock_entry.data[CONF_EMAIL] == "test-email3"
assert mock_entry.data[CONF_PASSWORD] == "test-password3"
assert config_entry.data[CONF_EMAIL] == "test-email3"
assert config_entry.data[CONF_PASSWORD] == "test-password3"
async def test_reauth_wrong_account(hass: HomeAssistant, smarttub_api, account) -> None:
async def test_reauth_wrong_account(
hass: HomeAssistant, smarttub_api, account, config_entry
) -> None:
"""Test reauthentication flow if the user enters credentials for a different already-configured account."""
mock_entry1 = MockConfigEntry(
domain=DOMAIN,
data={CONF_EMAIL: "test-email1", CONF_PASSWORD: "test-password1"},
unique_id=account.id,
)
mock_entry1.add_to_hass(hass)
config_entry.add_to_hass(hass)
mock_entry2 = MockConfigEntry(
domain=DOMAIN,
@@ -98,7 +108,7 @@ async def test_reauth_wrong_account(hass: HomeAssistant, smarttub_api, account)
mock_entry2.add_to_hass(hass)
# we try to reauth account #2, and the user successfully authenticates to account #1
account.id = mock_entry1.unique_id
account.id = config_entry.unique_id
result = await mock_entry2.start_reauth_flow(hass)
assert result["type"] is FlowResultType.FORM

View File

@@ -1,13 +1,10 @@
"""Test smarttub setup process."""
from unittest.mock import patch
from smarttub import LoginFailed
from homeassistant.components.smarttub.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
async def test_setup_with_no_config(
@@ -39,34 +36,23 @@ async def test_setup_auth_failed(
smarttub_api.login.side_effect = LoginFailed
config_entry.add_to_hass(hass)
with patch.object(hass.config_entries.flow, "async_init") as mock_flow_init:
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_ERROR
mock_flow_init.assert_called_with(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"entry_id": config_entry.entry_id,
"unique_id": config_entry.unique_id,
"title_placeholders": {"name": config_entry.title},
},
data=config_entry.data,
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_ERROR
async def test_config_passed_to_config_entry(
hass: HomeAssistant, config_entry, config_data
) -> None:
"""Test that configured options are loaded via config entry."""
config_entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, config_data)
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
assert len(flows) == 1
assert flows[0]["context"]["source"] == SOURCE_REAUTH
async def test_unload_entry(hass: HomeAssistant, config_entry) -> None:
"""Test being able to unload an entry."""
config_entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {}) is True
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert config_entry.state is ConfigEntryState.NOT_LOADED

View File

@@ -7,49 +7,73 @@ from homeassistant.core import HomeAssistant
@pytest.mark.parametrize(
("pump_id", "entity_suffix", "pump_state"),
("pump_id", "entity_suffix", "expected_state"),
[
("CP", "circulation_pump", "off"),
("P1", "jet_p1", "off"),
("P2", "jet_p2", "on"),
("CP", "circulation_pump", STATE_OFF),
("P1", "jet_p1", STATE_OFF),
("P2", "jet_p2", STATE_ON),
],
)
async def test_pumps(
spa, setup_entry, hass: HomeAssistant, pump_id, pump_state, entity_suffix
async def test_pump_state(
spa, setup_entry, hass: HomeAssistant, pump_id, entity_suffix, expected_state
) -> None:
"""Test pump entities."""
status = await spa.get_status_full()
pump = next(pump for pump in status.pumps if pump.id == pump_id)
"""Test pump entity initial state."""
entity_id = f"switch.{spa.brand}_{spa.model}_{entity_suffix}"
state = hass.states.get(entity_id)
assert state is not None
assert state.state == pump_state
assert state.state == expected_state
@pytest.mark.parametrize(
("pump_id", "entity_suffix"),
[
("CP", "circulation_pump"),
("P1", "jet_p1"),
("P2", "jet_p2"),
],
)
async def test_pump_toggle(
spa, setup_entry, hass: HomeAssistant, pump_id, entity_suffix
) -> None:
"""Test toggling a pump."""
status = await spa.get_status_full()
pump = next(pump for pump in status.pumps if pump.id == pump_id)
entity_id = f"switch.{spa.brand}_{spa.model}_{entity_suffix}"
await hass.services.async_call(
"switch",
"toggle",
{"entity_id": entity_id},
blocking=True,
"switch", "toggle", {"entity_id": entity_id}, blocking=True
)
pump.toggle.assert_called()
if state.state == STATE_OFF:
await hass.services.async_call(
"switch",
"turn_on",
{"entity_id": entity_id},
blocking=True,
)
pump.toggle.assert_called()
else:
assert state.state == STATE_ON
await hass.services.async_call(
"switch",
"turn_off",
{"entity_id": entity_id},
blocking=True,
)
pump.toggle.assert_called()
@pytest.mark.parametrize(
("pump_id", "entity_suffix"),
[
("CP", "circulation_pump"),
("P1", "jet_p1"),
],
)
async def test_pump_turn_on(
spa, setup_entry, hass: HomeAssistant, pump_id, entity_suffix
) -> None:
"""Test turning on an off pump toggles it."""
status = await spa.get_status_full()
pump = next(pump for pump in status.pumps if pump.id == pump_id)
entity_id = f"switch.{spa.brand}_{spa.model}_{entity_suffix}"
await hass.services.async_call(
"switch", "turn_on", {"entity_id": entity_id}, blocking=True
)
pump.toggle.assert_called()
async def test_pump_turn_off(spa, setup_entry, hass: HomeAssistant) -> None:
"""Test turning off an on pump toggles it."""
status = await spa.get_status_full()
pump = next(pump for pump in status.pumps if pump.id == "P2")
entity_id = f"switch.{spa.brand}_{spa.model}_jet_p2"
await hass.services.async_call(
"switch", "turn_off", {"entity_id": entity_id}, blocking=True
)
pump.toggle.assert_called()