Major rework of tests and refactoring

This commit is contained in:
Robin Lintermann 2025-05-10 12:07:55 +00:00
parent 8893f97f95
commit d5e4ed2b9e
6 changed files with 114 additions and 68 deletions

View File

@ -14,7 +14,7 @@ type FederwiegeConfigEntry = ConfigEntry[Federwiege]
async def async_setup_entry(hass: HomeAssistant, entry: FederwiegeConfigEntry) -> bool:
"""Set up this integration using UI."""
connection = Connection(HOST, token_b64=entry.data.get(CONF_ACCESS_TOKEN, None))
connection = Connection(HOST, token_b64=entry.data.get(CONF_ACCESS_TOKEN))
# Check if token still has access
if not await connection.refresh_token():

View File

@ -5,6 +5,7 @@ from __future__ import annotations
from typing import Any
from pysmarlaapi import Connection
from pysmarlaapi.classes import AuthToken
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@ -20,23 +21,21 @@ class SmarlaConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
async def _handle_token(self, token: str) -> tuple[dict[str, str], dict[str, str]]:
async def _handle_token(self, token: str) -> tuple[dict[str, str], AuthToken]:
"""Handle the token input."""
errors: dict[str, str] = {}
info: dict[str, str] = {}
try:
conn = Connection(url=HOST, token_b64=token)
except ValueError:
errors["base"] = "invalid_token"
return (errors, info)
errors["base"] = "malformed_token"
return (errors, None)
if await conn.refresh_token():
info["serial_number"] = conn.token.serialNumber
else:
if not await conn.refresh_token():
errors["base"] = "invalid_auth"
return (errors, None)
return (errors, info)
return (errors, conn.token)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -45,19 +44,16 @@ class SmarlaConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
token = user_input[CONF_ACCESS_TOKEN]
errors, info = await self._handle_token(token=token)
raw_token = user_input[CONF_ACCESS_TOKEN]
errors, token = await self._handle_token(token=raw_token)
if not errors:
serial_number = info["serial_number"]
await self.async_set_unique_id(serial_number)
await self.async_set_unique_id(token.serialNumber)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=serial_number,
data={CONF_ACCESS_TOKEN: token},
title=token.serialNumber,
data={CONF_ACCESS_TOKEN: raw_token},
)
return self.async_show_form(

View File

@ -2,7 +2,7 @@
"config": {
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_token": "Invalid access token"
"malformed_token": "Malformed access token"
},
"step": {
"user": {

View File

@ -1 +1,20 @@
"""Tests for the Smarla integration."""
import base64
import json
from homeassistant.const import CONF_ACCESS_TOKEN
MOCK_ACCESS_TOKEN_JSON = {
"refreshToken": "test",
"appIdentifier": "HA-test",
"serialNumber": "ABCD",
}
MOCK_SERIAL_NUMBER = MOCK_ACCESS_TOKEN_JSON["serialNumber"]
MOCK_ACCESS_TOKEN = base64.b64encode(
json.dumps(MOCK_ACCESS_TOKEN_JSON).encode()
).decode()
MOCK_USER_INPUT = {CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN}

View File

@ -0,0 +1,44 @@
"""Configuration for Sentry tests."""
from __future__ import annotations
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant.components.smarla.config_flow import Connection
from homeassistant.components.smarla.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from . import MOCK_SERIAL_NUMBER
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Create a mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_SERIAL_NUMBER,
source=SOURCE_USER,
)
@pytest.fixture
def mock_refresh_token_success():
"""Patch Connection.refresh_token to return True."""
with patch.object(Connection, "refresh_token", new=AsyncMock(return_value=True)):
yield
@pytest.fixture
def malformed_token_patch():
"""Patch Connection to raise exception."""
return patch.object(Connection, "__init__", side_effect=ValueError)
@pytest.fixture
def invalid_auth_patch():
"""Patch Connection.refresh_token to return False."""
return patch.object(Connection, "refresh_token", new=AsyncMock(return_value=False))

View File

@ -1,85 +1,72 @@
"""Test config flow for Swing2Sleep Smarla integration."""
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant import config_entries
from homeassistant.components.smarla.config_flow import Connection
from homeassistant.components.smarla.const import DOMAIN
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from . import MOCK_SERIAL_NUMBER, MOCK_USER_INPUT
from tests.common import MockConfigEntry
MOCK_ACCESS_TOKEN = "eyJyZWZyZXNoVG9rZW4iOiJ0ZXN0IiwiYXBwSWRlbnRpZmllciI6IkhBLXRlc3QiLCJzZXJpYWxOdW1iZXIiOiJBQkNEIn0="
MOCK_SERIAL_NUMBER = "ABCD"
async def test_show_form(hass: HomeAssistant) -> None:
"""Test that the form is shown initially."""
async def test_config_flow(hass: HomeAssistant, mock_refresh_token_success) -> None:
"""Test creating a config entry."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "user"}
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "user"
async def test_create_entry(hass: HomeAssistant) -> None:
"""Test creating a config entry."""
with patch.object(Connection, "refresh_token", new=AsyncMock(return_value=True)):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN},
)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_INPUT
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == MOCK_SERIAL_NUMBER
assert result["data"] == {CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN}
assert result["data"] == MOCK_USER_INPUT
assert result["result"].unique_id == MOCK_SERIAL_NUMBER
async def test_invalid_auth(hass: HomeAssistant) -> None:
@pytest.mark.parametrize("error", ["malformed_token", "invalid_auth"])
async def test_form_error(
hass: HomeAssistant, request: pytest.FixtureRequest, error: str
) -> None:
"""Test we show user form on invalid auth."""
with patch.object(Connection, "refresh_token", new=AsyncMock(return_value=None)):
error_patch = request.getfixturevalue(f"{error}_patch")
with error_patch:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN},
context={"source": SOURCE_USER},
data=MOCK_USER_INPUT,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "invalid_auth"}
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}
request.getfixturevalue("mock_refresh_token_success")
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=MOCK_USER_INPUT
)
assert result["type"] == FlowResultType.CREATE_ENTRY
async def test_invalid_token(hass: HomeAssistant) -> None:
"""Test we handle invalid/malformed tokens."""
async def test_device_exists_abort(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_refresh_token_success
) -> None:
"""Test we abort config flow if Smarla device already configured."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={CONF_ACCESS_TOKEN: "invalid_token"},
context={"source": SOURCE_USER},
data=MOCK_USER_INPUT,
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {"base": "invalid_token"}
async def test_device_exists_abort(hass: HomeAssistant) -> None:
"""Test we abort config flow if Smarla device already configured."""
config_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_SERIAL_NUMBER,
source=config_entries.SOURCE_USER,
)
config_entry.add_to_hass(hass)
with patch.object(Connection, "refresh_token", new=AsyncMock(return_value=True)):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data={CONF_ACCESS_TOKEN: MOCK_ACCESS_TOKEN},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
assert len(hass.config_entries.async_entries(DOMAIN)) == 1