Add wattpilot goecharger and goecontroller integration with autodiscovery

This commit is contained in:
2023-05-24 17:36:45 +02:00
parent 3dc3de95fa
commit 36bb5be907
28 changed files with 1016 additions and 0 deletions

View File

@@ -517,6 +517,10 @@ build.json @home-assistant/supervisor
/tests/components/goalzero/ @tkdrob
/homeassistant/components/gogogate2/ @vangorra
/tests/components/gogogate2/ @vangorra
/homeassistant/components/goecharger/ @0xFEEDC0DE64
/tests/components/goecharger/ @0xFEEDC0DE64
/homeassistant/components/goecontroller/ @0xFEEDC0DE64
/tests/components/goecontroller/ @0xFEEDC0DE64
/homeassistant/components/goodwe/ @mletenay @starkillerOG
/tests/components/goodwe/ @mletenay @starkillerOG
/homeassistant/components/google/ @allenporter
@@ -1547,6 +1551,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/water_heater/ @home-assistant/core
/tests/components/water_heater/ @home-assistant/core
/homeassistant/components/watson_tts/ @rutkai
/homeassistant/components/wattpilot/ @0xFEEDC0DE64
/tests/components/wattpilot/ @0xFEEDC0DE64
/homeassistant/components/watttime/ @bachya
/tests/components/watttime/ @bachya
/homeassistant/components/waze_travel_time/ @eifinger

View File

@@ -0,0 +1,49 @@
"""The go-eCharger integration."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO List the platforms that you want to support.
# For your initial PR, limit it to 1 platform.
PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the go-eCharger integration."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up go-eCharger from a config entry."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
hass.data.setdefault(DOMAIN, {})
# TODO 1. Create API instance
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -0,0 +1,123 @@
"""Config flow for go-eCharger integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO adjust the data schema to the data that you need
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
vol.Required("username"): str,
vol.Required("password"): str,
}
)
class PlaceholderHub:
"""Placeholder class to make tests pass.
TODO Remove this placeholder class and replace with things from your PyPI package.
"""
def __init__(self, host: str) -> None:
"""Initialize."""
self.host = host
async def authenticate(self, username: str, password: str) -> bool:
"""Test if we can authenticate with the host."""
return True
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
# TODO validate the data can be used to set up a connection.
# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data["username"], data["password"]
# )
hub = PlaceholderHub(data["host"])
if not await hub.authenticate(data["username"], data["password"]):
raise InvalidAuth
# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth
# Return info that you want to store in the config entry.
return {"title": "Name of the device"}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for go-eCharger."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
serial = discovery_info.properties.get("serial")
if serial is None:
return self.async_abort(reason="mdns_missing_serial")
self.context["title_placeholders"] = {"name": serial}
# Check if already configured
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured(updates={})
return self.async_show_form(
step_id="user", description_placeholders={"serial": serial}
)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""

View File

@@ -0,0 +1,3 @@
"""Constants for the go-eCharger integration."""
DOMAIN = "goecharger"

View File

@@ -0,0 +1,16 @@
{
"domain": "goecharger",
"name": "go-eCharger",
"after_dependencies": ["zeroconf"],
"codeowners": ["@0xFEEDC0DE64"],
"config_flow": true,
"dependencies": [],
"documentation": "https://www.home-assistant.io/integrations/goecharger",
"integration_type": "device",
"iot_class": "local_push",
"requirements": [],
"zeroconf": [
"_go-e_go-eCharger_V4._tcp.local.",
"_go-e_go-eCharger._tcp.local."
]
}

View File

@@ -0,0 +1,23 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"flow_title": "{serial}",
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"mdns_missing_serial": "Missing serial number in MDNS properties."
}
}
}

View File

@@ -0,0 +1,49 @@
"""The go-e Controller integration."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO List the platforms that you want to support.
# For your initial PR, limit it to 1 platform.
PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the go-eCharger integration."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up go-e Controller from a config entry."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
hass.data.setdefault(DOMAIN, {})
# TODO 1. Create API instance
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -0,0 +1,123 @@
"""Config flow for go-e Controller integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO adjust the data schema to the data that you need
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
vol.Required("username"): str,
vol.Required("password"): str,
}
)
class PlaceholderHub:
"""Placeholder class to make tests pass.
TODO Remove this placeholder class and replace with things from your PyPI package.
"""
def __init__(self, host: str) -> None:
"""Initialize."""
self.host = host
async def authenticate(self, username: str, password: str) -> bool:
"""Test if we can authenticate with the host."""
return True
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
# TODO validate the data can be used to set up a connection.
# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data["username"], data["password"]
# )
hub = PlaceholderHub(data["host"])
if not await hub.authenticate(data["username"], data["password"]):
raise InvalidAuth
# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth
# Return info that you want to store in the config entry.
return {"title": "Name of the device"}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for go-e Controller."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
serial = discovery_info.properties.get("serial")
if serial is None:
return self.async_abort(reason="mdns_missing_serial")
self.context["title_placeholders"] = {"name": serial}
# Check if already configured
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured(updates={})
return self.async_show_form(
step_id="user", description_placeholders={"serial": serial}
)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""

View File

@@ -0,0 +1,3 @@
"""Constants for the go-e Controller integration."""
DOMAIN = "goecontroller"

View File

@@ -0,0 +1,16 @@
{
"domain": "goecontroller",
"name": "go-e Controller",
"after_dependencies": ["zeroconf"],
"codeowners": ["@0xFEEDC0DE64"],
"config_flow": true,
"dependencies": [],
"documentation": "https://www.home-assistant.io/integrations/goecontroller",
"integration_type": "device",
"iot_class": "local_push",
"requirements": [],
"zeroconf": [
"_go-e_controller._tcp.local.",
"_go-e_controller-lite._tcp._tcp.local."
]
}

View File

@@ -0,0 +1,23 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"flow_title": "{serial}",
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"mdns_missing_serial": "Missing serial number in MDNS properties."
}
}
}

View File

@@ -0,0 +1,48 @@
"""The Fronius Wattpilot integration."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO List the platforms that you want to support.
# For your initial PR, limit it to 1 platform.
PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up the go-eCharger integration."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Fronius Wattpilot from a config entry."""
# zeroconf_instance = await zeroconf.async_get_instance(hass)
hass.data.setdefault(DOMAIN, {})
# TODO 1. Create API instance
# TODO 2. Validate the API connection (and authentication)
# TODO 3. Store an API object for your platforms to access
# hass.data[DOMAIN][entry.entry_id] = MyApi(...)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok

View File

@@ -0,0 +1,123 @@
"""Config flow for Fronius Wattpilot integration."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import zeroconf
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
# TODO adjust the data schema to the data that you need
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
vol.Required("username"): str,
vol.Required("password"): str,
}
)
class PlaceholderHub:
"""Placeholder class to make tests pass.
TODO Remove this placeholder class and replace with things from your PyPI package.
"""
def __init__(self, host: str) -> None:
"""Initialize."""
self.host = host
async def authenticate(self, username: str, password: str) -> bool:
"""Test if we can authenticate with the host."""
return True
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
# TODO validate the data can be used to set up a connection.
# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data["username"], data["password"]
# )
hub = PlaceholderHub(data["host"])
if not await hub.authenticate(data["username"], data["password"]):
raise InvalidAuth
# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth
# Return info that you want to store in the config entry.
return {"title": "Name of the device"}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Fronius Wattpilot."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> FlowResult:
"""Handle zeroconf discovery."""
serial = discovery_info.properties.get("serial")
if serial is None:
return self.async_abort(reason="mdns_missing_serial")
self.context["title_placeholders"] = {"name": serial}
# Check if already configured
await self.async_set_unique_id(serial)
self._abort_if_unique_id_configured(updates={})
return self.async_show_form(
step_id="user", description_placeholders={"serial": serial}
)
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""
class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""

View File

@@ -0,0 +1,3 @@
"""Constants for the Fronius Wattpilot integration."""
DOMAIN = "wattpilot"

View File

@@ -0,0 +1,16 @@
{
"domain": "wattpilot",
"name": "Fronius Wattpilot",
"after_dependencies": ["zeroconf"],
"codeowners": ["@0xFEEDC0DE64"],
"config_flow": true,
"dependencies": [],
"documentation": "https://www.home-assistant.io/integrations/wattpilot",
"integration_type": "device",
"iot_class": "local_push",
"requirements": [],
"zeroconf": [
"_fronius_wattpilot._tcp.local.",
"_fronius_wattpilot_V2._tcp.local."
]
}

View File

@@ -0,0 +1,23 @@
{
"config": {
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"flow_title": "{serial}",
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"mdns_missing_serial": "Missing serial number in MDNS properties."
}
}
}

View File

@@ -201,6 +201,8 @@ FLOWS = {
"github",
"glances",
"goalzero",
"goecharger",
"goecontroller",
"gogogate2",
"goodwe",
"google",
@@ -599,6 +601,7 @@ FLOWS = {
"vulcan",
"wallbox",
"waqi",
"wattpilot",
"watttime",
"waze_travel_time",
"weatherflow",

View File

@@ -2222,6 +2222,18 @@
"config_flow": true,
"iot_class": "local_polling"
},
"goecharger": {
"name": "go-eCharger",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
"goecontroller": {
"name": "go-e Controller",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
"gogogate2": {
"name": "Gogogate2 and ismartgate",
"integration_type": "hub",
@@ -6704,6 +6716,12 @@
"config_flow": false,
"iot_class": "cloud_polling"
},
"wattpilot": {
"name": "Fronius Wattpilot",
"integration_type": "device",
"config_flow": true,
"iot_class": "local_push"
},
"watttime": {
"name": "WattTime",
"integration_type": "service",

View File

@@ -465,6 +465,36 @@ ZEROCONF = {
"domain": "freebox",
},
],
"_fronius_wattpilot._tcp.local.": [
{
"domain": "wattpilot",
},
],
"_fronius_wattpilot_V2._tcp.local.": [
{
"domain": "wattpilot",
},
],
"_go-e_controller-lite._tcp._tcp.local.": [
{
"domain": "goecontroller",
},
],
"_go-e_controller._tcp.local.": [
{
"domain": "goecontroller",
},
],
"_go-e_go-eCharger._tcp.local.": [
{
"domain": "goecharger",
},
],
"_go-e_go-eCharger_V4._tcp.local.": [
{
"domain": "goecharger",
},
],
"_googlecast._tcp.local.": [
{
"domain": "cast",

View File

@@ -0,0 +1 @@
"""Tests for the go-eCharger integration."""

View File

@@ -0,0 +1,14 @@
"""Common fixtures for the go-eCharger tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.goecharger.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -0,0 +1,90 @@
"""Test the go-eCharger config flow."""
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant import config_entries
from homeassistant.components.goecharger.config_flow import CannotConnect, InvalidAuth
from homeassistant.components.goecharger.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] is None
with patch(
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Name of the device"
assert result2["data"] == {
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
side_effect=InvalidAuth,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.goecharger.config_flow.PlaceholderHub.authenticate",
side_effect=CannotConnect,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}

View File

@@ -0,0 +1 @@
"""Tests for the go-e Controller integration."""

View File

@@ -0,0 +1,14 @@
"""Common fixtures for the go-e Controller tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.goecontroller.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -0,0 +1,93 @@
"""Test the go-e Controller config flow."""
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant import config_entries
from homeassistant.components.goecontroller.config_flow import (
CannotConnect,
InvalidAuth,
)
from homeassistant.components.goecontroller.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] is None
with patch(
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Name of the device"
assert result2["data"] == {
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
side_effect=InvalidAuth,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.goecontroller.config_flow.PlaceholderHub.authenticate",
side_effect=CannotConnect,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}

View File

@@ -0,0 +1 @@
"""Tests for the Fronius Wattpilot integration."""

View File

@@ -0,0 +1,14 @@
"""Common fixtures for the Fronius Wattpilot tests."""
from collections.abc import Generator
from unittest.mock import AsyncMock, patch
import pytest
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock, None, None]:
"""Override async_setup_entry."""
with patch(
"homeassistant.components.wattpilot.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry

View File

@@ -0,0 +1,90 @@
"""Test the Fronius Wattpilot config flow."""
from unittest.mock import AsyncMock, patch
import pytest
from homeassistant import config_entries
from homeassistant.components.wattpilot.config_flow import CannotConnect, InvalidAuth
from homeassistant.components.wattpilot.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == FlowResultType.FORM
assert result["errors"] is None
with patch(
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.CREATE_ENTRY
assert result2["title"] == "Name of the device"
assert result2["data"] == {
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
side_effect=InvalidAuth,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
"""Test we handle cannot connect error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"homeassistant.components.wattpilot.config_flow.PlaceholderHub.authenticate",
side_effect=CannotConnect,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"host": "1.1.1.1",
"username": "test-username",
"password": "test-password",
},
)
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}