Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion custom_components/touchline/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

from __future__ import annotations

from typing import override

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import httpx_client
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
from httpx import AsyncClient
from pytouchline_extended import PyTouchline

from .const import _LOGGER, DOMAIN

PLATFORMS = [Platform.CLIMATE]
Expand Down Expand Up @@ -75,7 +80,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
host,
)

py_touchline = PyTouchline(url=host)
py_touchline = HassTouchline(hass=hass, url=host)
number_of_devices = await py_touchline.get_number_of_devices_async()

_LOGGER.debug(
Expand Down Expand Up @@ -104,3 +109,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


class HassTouchline(PyTouchline):
def __init__(self, hass: HomeAssistant, url: str):
super().__init__(url=url)
self.hass = hass
self._async_client = httpx_client.create_async_httpx_client(
hass,
verify_ssl=False,
timeout=self._timeout,
)

@override
def get_async_client(self) -> AsyncClient:
return self._async_client
1 change: 1 addition & 0 deletions custom_components/touchline/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from pytouchline_extended import PyTouchline

from .const import _LOGGER, DOMAIN


Expand Down
10 changes: 6 additions & 4 deletions custom_components/touchline/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST
from pytouchline_extended import PyTouchline

from .const import _LOGGER, DOMAIN

DATA_SCHEMA = vol.Schema(
Expand All @@ -25,7 +26,7 @@
async def _async_try_connect_and_fetch_basic_info(host):
"""Attempt to connect and, if successful, fetch number of devices."""
py_touchline = PyTouchline(url=host)
result = {"type": None, "data": {}}
result = {"type": None, "data": None}
number_of_devices = None
device = PyTouchline(id=0, url=host)
try:
Expand Down Expand Up @@ -65,7 +66,6 @@ async def async_step_user(
result = {"type": None, "data": {}}

if user_input is not None:

# Abort if an entry with same host is present.
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})

Expand Down Expand Up @@ -93,12 +93,14 @@ async def async_step_user(
user_input[CONF_HOST],
)
if not errors:
return self.async_create_entry(title=user_input[CONF_HOST], data=user_input)
return self.async_create_entry(
title=user_input[CONF_HOST], data=user_input
)

return self.async_show_form(
step_id="user",
data_schema=DATA_SCHEMA,
errors = errors,
errors=errors,
)

async def async_step_import(self, user_input: dict[str, Any]) -> ConfigFlowResult:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/touchline/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "touchline",
"name": "Roth Touchline",
"version": "0.1.2",
"version": "0.1.3",
"integration_type": "hub",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/touchline",
Expand Down
29 changes: 29 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "hass-touchline"
version = "0.1.3"
description = "Home Assistant custom component using pytouchline_extended lib."
readme = "README.md"
authors = [
{ name = "Peter Brøndum", email = "peter.brondum@bestseller.com" },
{ name = "Hack Kampbjørn", email = "com.github@hack.kampbjorn.com" }
]
dependencies = [
"pytouchline_extended==1.1.2"
]
requires-python = ">=3.12"

[project.optional-dependencies]
test = [
"pytest==9.0.0",
"pytest-homeassistant-custom-component==0.13.315"
]

[tool.pytest.ini_options]
testpaths = ["tests"]
norecursedirs = [".git", "venv-*"]
asyncio_mode = "auto"
pythonpath = ["."]
67 changes: 67 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Tests for Roth Touchline setup."""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

# Import from the integration
from custom_components.touchline import async_setup_entry
from custom_components.touchline.const import DOMAIN


@pytest.fixture
def mock_config_entry():
"""Mock a config entry."""
entry = MagicMock()
entry.data = {CONF_HOST: "http://127.0.0.2"}
entry.entry_id = "test_entry_id"
entry.state = ConfigEntryState.NOT_LOADED
return entry


@patch("custom_components.touchline.HassTouchline")
async def test_setup_entry_success(
mock_hass_touchline, hass: HomeAssistant, mock_config_entry
):
"""Test successful setup of a config entry."""
mock_instance = mock_hass_touchline.return_value
mock_instance.get_number_of_devices_async = AsyncMock(return_value=5)

with patch.object(
hass.config_entries, "async_forward_entry_setups", return_value=True
) as mock_forward:
assert await async_setup_entry(hass, mock_config_entry) is True

assert DOMAIN in hass.data
assert hass.data[DOMAIN][mock_config_entry.entry_id] == mock_instance
assert mock_forward.called


@patch("custom_components.touchline.HassTouchline")
async def test_setup_entry_no_devices(
mock_hass_touchline, hass: HomeAssistant, mock_config_entry
):
"""Test setup failure when no devices are found."""
mock_instance = mock_hass_touchline.return_value
mock_instance.get_number_of_devices_async = AsyncMock(return_value=0)

with pytest.raises(ConfigEntryNotReady):
await async_setup_entry(hass, mock_config_entry)


@patch("custom_components.touchline.HassTouchline")
async def test_setup_entry_exception(
mock_hass_touchline, hass: HomeAssistant, mock_config_entry
):
"""Test setup failure when an exception occurs during device check."""
mock_instance = mock_hass_touchline.return_value
mock_instance.get_number_of_devices_async = AsyncMock(
side_effect=Exception("Connection error")
)

with pytest.raises(Exception):
await async_setup_entry(hass, mock_config_entry)