Support for multiple contracts in Prosegur (#89097)

* support for multiple contracts - tested live

* update tests

* address review

* address review

* fix
This commit is contained in:
Diogo Gomes 2023-04-25 14:16:51 +01:00 committed by GitHub
parent 1fa82fa886
commit a3048234d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 45 deletions

View File

@ -72,7 +72,7 @@ class ProsegurAlarm(alarm.AlarmControlPanelEntity):
"""Update alarm status.""" """Update alarm status."""
try: try:
self._installation = await Installation.retrieve(self._auth) self._installation = await Installation.retrieve(self._auth, self.contract)
except ConnectionError as err: except ConnectionError as err:
_LOGGER.error(err) _LOGGER.error(err)
self._attr_available = False self._attr_available = False

View File

@ -34,7 +34,9 @@ async def async_setup_entry(
"async_request_image", "async_request_image",
) )
_installation = await Installation.retrieve(hass.data[DOMAIN][entry.entry_id]) _installation = await Installation.retrieve(
hass.data[DOMAIN][entry.entry_id], entry.data["contract"]
)
async_add_entities( async_add_entities(
[ [

View File

@ -11,9 +11,9 @@ from homeassistant import config_entries, core, exceptions
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client, selector
from .const import CONF_COUNTRY, DOMAIN from .const import CONF_CONTRACT, CONF_COUNTRY, DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -31,27 +31,22 @@ async def validate_input(hass: core.HomeAssistant, data):
session = aiohttp_client.async_get_clientsession(hass) session = aiohttp_client.async_get_clientsession(hass)
auth = Auth(session, data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_COUNTRY]) auth = Auth(session, data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_COUNTRY])
try: try:
install = await Installation.retrieve(auth) contracts = await Installation.list(auth)
return auth, contracts
except ConnectionRefusedError: except ConnectionRefusedError:
raise InvalidAuth from ConnectionRefusedError raise InvalidAuth from ConnectionRefusedError
except ConnectionError: except ConnectionError:
raise CannotConnect from ConnectionError raise CannotConnect from ConnectionError
# Info to store in the config entry.
return {
"title": f"Contract {install.contract}",
"contract": install.contract,
"username": data[CONF_USERNAME],
"password": data[CONF_PASSWORD],
"country": data[CONF_COUNTRY],
}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Prosegur Alarm.""" """Handle a config flow for Prosegur Alarm."""
VERSION = 1 VERSION = 1
entry: ConfigEntry entry: ConfigEntry
auth: Auth
user_input: dict
contracts: list[dict[str, str]]
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle the initial step.""" """Handle the initial step."""
@ -59,7 +54,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input: if user_input:
try: try:
info = await validate_input(self.hass, user_input) self.auth, self.contracts = await validate_input(self.hass, user_input)
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuth: except InvalidAuth:
@ -68,16 +63,44 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_LOGGER.exception(exception) _LOGGER.exception(exception)
errors["base"] = "unknown" errors["base"] = "unknown"
else: else:
await self.async_set_unique_id(info["contract"]) self.user_input = user_input
self._abort_if_unique_id_configured() return await self.async_step_choose_contract()
user_input["contract"] = info["contract"]
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
) )
async def async_step_choose_contract(
self, user_input: Any | None = None
) -> FlowResult:
"""Let user decide which contract is being setup."""
if user_input:
await self.async_set_unique_id(user_input[CONF_CONTRACT])
self._abort_if_unique_id_configured()
self.user_input[CONF_CONTRACT] = user_input[CONF_CONTRACT]
return self.async_create_entry(
title=f"Contract {user_input[CONF_CONTRACT]}", data=self.user_input
)
contract_options = [
selector.SelectOptionDict(value=c["contractId"], label=c["description"])
for c in self.contracts
]
return self.async_show_form(
step_id="choose_contract",
data_schema=vol.Schema(
{
vol.Required(CONF_CONTRACT): selector.SelectSelector(
selector.SelectSelectorConfig(options=contract_options)
),
}
),
)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult: async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle initiation of re-authentication with Prosegur.""" """Handle initiation of re-authentication with Prosegur."""
self.entry = cast( self.entry = cast(
@ -93,7 +116,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input: if user_input:
try: try:
user_input[CONF_COUNTRY] = self.entry.data[CONF_COUNTRY] user_input[CONF_COUNTRY] = self.entry.data[CONF_COUNTRY]
await validate_input(self.hass, user_input) self.auth, self.contracts = await validate_input(self.hass, user_input)
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"

View File

@ -3,5 +3,6 @@
DOMAIN = "prosegur" DOMAIN = "prosegur"
CONF_COUNTRY = "country" CONF_COUNTRY = "country"
CONF_CONTRACT = "contract"
SERVICE_REQUEST_IMAGE = "request_image" SERVICE_REQUEST_IMAGE = "request_image"

View File

@ -9,7 +9,7 @@ from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from .const import CONF_CONTRACT, DOMAIN
TO_REDACT = {"description", "latitude", "longitude", "contractId", "address"} TO_REDACT = {"description", "latitude", "longitude", "contractId", "address"}
@ -19,7 +19,9 @@ async def async_get_config_entry_diagnostics(
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
installation = await Installation.retrieve(hass.data[DOMAIN][entry.entry_id]) installation = await Installation.retrieve(
hass.data[DOMAIN][entry.entry_id], entry.data[CONF_CONTRACT]
)
activity = await installation.activity(hass.data[DOMAIN][entry.entry_id]) activity = await installation.activity(hass.data[DOMAIN][entry.entry_id])

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/prosegur", "documentation": "https://www.home-assistant.io/integrations/prosegur",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["pyprosegur"], "loggers": ["pyprosegur"],
"requirements": ["pyprosegur==0.0.8"] "requirements": ["pyprosegur==0.0.9"]
} }

View File

@ -8,6 +8,11 @@
"country": "Country" "country": "Country"
} }
}, },
"choose_contract": {
"data": {
"contract": "Contract"
}
},
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
"description": "Re-authenticate with Prosegur account.", "description": "Re-authenticate with Prosegur account.",

View File

@ -1886,7 +1886,7 @@ pypoint==2.3.0
pyprof2calltree==1.4.5 pyprof2calltree==1.4.5
# homeassistant.components.prosegur # homeassistant.components.prosegur
pyprosegur==0.0.8 pyprosegur==0.0.9
# homeassistant.components.prusalink # homeassistant.components.prusalink
pyprusalink==1.1.0 pyprusalink==1.1.0

View File

@ -1381,7 +1381,7 @@ pypoint==2.3.0
pyprof2calltree==1.4.5 pyprof2calltree==1.4.5
# homeassistant.components.prosegur # homeassistant.components.prosegur
pyprosegur==0.0.8 pyprosegur==0.0.9
# homeassistant.components.prusalink # homeassistant.components.prusalink
pyprusalink==1.1.0 pyprusalink==1.1.0

View File

@ -27,6 +27,15 @@ def mock_config_entry() -> MockConfigEntry:
) )
@pytest.fixture
def mock_list_contracts() -> AsyncMock:
"""Return list of contracts per user."""
return [
{"contractId": "123", "description": "a b c"},
{"contractId": "456", "description": "x y z"},
]
@pytest.fixture @pytest.fixture
def mock_install() -> AsyncMock: def mock_install() -> AsyncMock:
"""Return the mocked alarm install.""" """Return the mocked alarm install."""

View File

@ -1,5 +1,5 @@
"""Test the Prosegur Alarm config flow.""" """Test the Prosegur Alarm config flow."""
from unittest.mock import MagicMock, patch from unittest.mock import patch
import pytest import pytest
@ -12,7 +12,7 @@ from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant) -> None: async def test_form(hass: HomeAssistant, mock_list_contracts) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -21,12 +21,9 @@ async def test_form(hass: HomeAssistant) -> None:
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] == {} assert result["errors"] == {}
install = MagicMock()
install.contract = "123"
with patch( with patch(
"homeassistant.components.prosegur.config_flow.Installation.retrieve", "homeassistant.components.prosegur.config_flow.Installation.list",
return_value=install, return_value=mock_list_contracts,
) as mock_retrieve, patch( ) as mock_retrieve, patch(
"homeassistant.components.prosegur.async_setup_entry", "homeassistant.components.prosegur.async_setup_entry",
return_value=True, return_value=True,
@ -41,9 +38,15 @@ async def test_form(hass: HomeAssistant) -> None:
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == "create_entry" result3 = await hass.config_entries.flow.async_configure(
assert result2["title"] == "Contract 123" result2["flow_id"],
assert result2["data"] == { {"contract": "123"},
)
await hass.async_block_till_done()
assert result3["type"] == "create_entry"
assert result3["title"] == "Contract 123"
assert result3["data"] == {
"contract": "123", "contract": "123",
"username": "test-username", "username": "test-username",
"password": "test-password", "password": "test-password",
@ -61,7 +64,7 @@ async def test_form_invalid_auth(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"pyprosegur.installation.Installation", "pyprosegur.installation.Installation.list",
side_effect=ConnectionRefusedError, side_effect=ConnectionRefusedError,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -84,7 +87,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
) )
with patch( with patch(
"pyprosegur.installation.Installation", "homeassistant.components.prosegur.config_flow.Installation.list",
side_effect=ConnectionError, side_effect=ConnectionError,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -123,7 +126,7 @@ async def test_form_unknown_exception(hass: HomeAssistant) -> None:
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
async def test_reauth_flow(hass: HomeAssistant) -> None: async def test_reauth_flow(hass: HomeAssistant, mock_list_contracts) -> None:
"""Test a reauthentication flow.""" """Test a reauthentication flow."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@ -149,12 +152,9 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
assert result["type"] == FlowResultType.FORM assert result["type"] == FlowResultType.FORM
assert result["errors"] == {} assert result["errors"] == {}
install = MagicMock()
install.contract = "123"
with patch( with patch(
"homeassistant.components.prosegur.config_flow.Installation.retrieve", "homeassistant.components.prosegur.config_flow.Installation.list",
return_value=install, return_value=mock_list_contracts,
) as mock_installation, patch( ) as mock_installation, patch(
"homeassistant.components.prosegur.async_setup_entry", "homeassistant.components.prosegur.async_setup_entry",
return_value=True, return_value=True,
@ -212,7 +212,7 @@ async def test_reauth_flow_error(hass: HomeAssistant, exception, base_error) ->
) )
with patch( with patch(
"homeassistant.components.prosegur.config_flow.Installation.retrieve", "homeassistant.components.prosegur.config_flow.Installation.list",
side_effect=exception, side_effect=exception,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(