mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 00:37:53 +00:00
Add Aussie Broadband integration (#53552)
This commit is contained in:
parent
1f00ded33a
commit
3dd7ec6856
@ -91,6 +91,8 @@ homeassistant/components/aurora/* @djtimca
|
||||
tests/components/aurora/* @djtimca
|
||||
homeassistant/components/aurora_abb_powerone/* @davet2001
|
||||
tests/components/aurora_abb_powerone/* @davet2001
|
||||
homeassistant/components/aussie_broadband/* @nickw444 @Bre77
|
||||
tests/components/aussie_broadband/* @nickw444 @Bre77
|
||||
homeassistant/components/auth/* @home-assistant/core
|
||||
tests/components/auth/* @home-assistant/core
|
||||
homeassistant/components/automation/* @home-assistant/core
|
||||
|
84
homeassistant/components/aussie_broadband/__init__.py
Normal file
84
homeassistant/components/aussie_broadband/__init__.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""The Aussie Broadband integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aussiebb.asyncio import AussieBB, AuthenticationException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import CONF_SERVICES, DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Aussie Broadband from a config entry."""
|
||||
# Login to the Aussie Broadband API and retrieve the current service list
|
||||
client = AussieBB(
|
||||
entry.data[CONF_USERNAME],
|
||||
entry.data[CONF_PASSWORD],
|
||||
async_get_clientsession(hass),
|
||||
)
|
||||
try:
|
||||
await client.login()
|
||||
all_services = await client.get_services()
|
||||
except AuthenticationException as exc:
|
||||
raise ConfigEntryAuthFailed() from exc
|
||||
except ClientError as exc:
|
||||
raise ConfigEntryNotReady() from exc
|
||||
|
||||
# Filter the service list to those that are enabled in options
|
||||
services = [
|
||||
s for s in all_services if str(s["service_id"]) in entry.options[CONF_SERVICES]
|
||||
]
|
||||
|
||||
# Create an appropriate refresh function
|
||||
def update_data_factory(service_id):
|
||||
async def async_update_data():
|
||||
return await client.get_usage(service_id)
|
||||
|
||||
return async_update_data
|
||||
|
||||
# Initiate a Data Update Coordinator for each service
|
||||
for service in services:
|
||||
service["coordinator"] = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=service["service_id"],
|
||||
update_interval=timedelta(minutes=DEFAULT_UPDATE_INTERVAL),
|
||||
update_method=update_data_factory(service[SERVICE_ID]),
|
||||
)
|
||||
await service["coordinator"].async_config_entry_first_refresh()
|
||||
|
||||
# Setup the integration
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||
"client": client,
|
||||
"services": services,
|
||||
}
|
||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(update_listener))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""Reload to update options."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload the config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
184
homeassistant/components/aussie_broadband/config_flow.py
Normal file
184
homeassistant/components/aussie_broadband/config_flow.py
Normal file
@ -0,0 +1,184 @@
|
||||
"""Config flow for Aussie Broadband integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
from aussiebb.asyncio import AussieBB, AuthenticationException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import CONF_SERVICES, DOMAIN, SERVICE_ID
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Aussie Broadband."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the config flow."""
|
||||
self.data: dict = {}
|
||||
self.options: dict = {CONF_SERVICES: []}
|
||||
self.services: list[dict[str]] = []
|
||||
self.client: AussieBB | None = None
|
||||
self._reauth_username: str | None = None
|
||||
|
||||
async def async_auth(self, user_input: dict[str, str]) -> dict[str, str] | None:
|
||||
"""Reusable Auth Helper."""
|
||||
self.client = AussieBB(
|
||||
user_input[CONF_USERNAME],
|
||||
user_input[CONF_PASSWORD],
|
||||
async_get_clientsession(self.hass),
|
||||
)
|
||||
try:
|
||||
await self.client.login()
|
||||
return None
|
||||
except AuthenticationException:
|
||||
return {"base": "invalid_auth"}
|
||||
except ClientError:
|
||||
return {"base": "cannot_connect"}
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] | None = None
|
||||
if user_input is not None:
|
||||
if not (errors := await self.async_auth(user_input)):
|
||||
await self.async_set_unique_id(user_input[CONF_USERNAME].lower())
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
self.data = user_input
|
||||
self.services = await self.client.get_services() # type: ignore[union-attr]
|
||||
|
||||
if not self.services:
|
||||
return self.async_abort(reason="no_services_found")
|
||||
|
||||
if len(self.services) == 1:
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_USERNAME],
|
||||
data=self.data,
|
||||
options={CONF_SERVICES: [str(self.services[0][SERVICE_ID])]},
|
||||
)
|
||||
|
||||
# Account has more than one service, select service to add
|
||||
return await self.async_step_service()
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_service(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle the optional service selection step."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
title=self.data[CONF_USERNAME], data=self.data, options=user_input
|
||||
)
|
||||
|
||||
service_options = {str(s[SERVICE_ID]): s["description"] for s in self.services}
|
||||
return self.async_show_form(
|
||||
step_id="service",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_SERVICES, default=list(service_options.keys())
|
||||
): cv.multi_select(service_options)
|
||||
}
|
||||
),
|
||||
errors=None,
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Handle reauth."""
|
||||
errors: dict[str, str] | None = None
|
||||
if user_input and user_input.get(CONF_USERNAME):
|
||||
self._reauth_username = user_input[CONF_USERNAME]
|
||||
|
||||
elif self._reauth_username and user_input and user_input.get(CONF_PASSWORD):
|
||||
data = {
|
||||
CONF_USERNAME: self._reauth_username,
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
}
|
||||
|
||||
if not (errors := await self.async_auth(data)):
|
||||
entry = await self.async_set_unique_id(self._reauth_username.lower())
|
||||
if entry:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
entry,
|
||||
data=data,
|
||||
)
|
||||
await self.hass.config_entries.async_reload(entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
return self.async_create_entry(title=self._reauth_username, data=data)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth",
|
||||
description_placeholders={"username": self._reauth_username},
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
) -> config_entries.OptionsFlow:
|
||||
"""Get the options flow for this handler."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Options flow for picking services."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
if self.config_entry.state != config_entries.ConfigEntryState.LOADED:
|
||||
return self.async_abort(reason="unknown")
|
||||
data = self.hass.data[DOMAIN][self.config_entry.entry_id]
|
||||
try:
|
||||
services = await data["client"].get_services()
|
||||
except AuthenticationException:
|
||||
return self.async_abort(reason="invalid_auth")
|
||||
except ClientError:
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
service_options = {str(s[SERVICE_ID]): s["description"] for s in services}
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required(
|
||||
CONF_SERVICES,
|
||||
default=self.config_entry.options.get(CONF_SERVICES),
|
||||
): cv.multi_select(service_options),
|
||||
}
|
||||
),
|
||||
)
|
5
homeassistant/components/aussie_broadband/const.py
Normal file
5
homeassistant/components/aussie_broadband/const.py
Normal file
@ -0,0 +1,5 @@
|
||||
"""Constants for the Aussie Broadband integration."""
|
||||
DEFAULT_UPDATE_INTERVAL = 30
|
||||
DOMAIN = "aussie_broadband"
|
||||
SERVICE_ID = "service_id"
|
||||
CONF_SERVICES = "services"
|
14
homeassistant/components/aussie_broadband/manifest.json
Normal file
14
homeassistant/components/aussie_broadband/manifest.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"domain": "aussie_broadband",
|
||||
"name": "Aussie Broadband",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aussie_broadband",
|
||||
"requirements": [
|
||||
"pyaussiebb==0.0.9"
|
||||
],
|
||||
"codeowners": [
|
||||
"@nickw444",
|
||||
"@Bre77"
|
||||
],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
140
homeassistant/components/aussie_broadband/sensor.py
Normal file
140
homeassistant/components/aussie_broadband/sensor.py
Normal file
@ -0,0 +1,140 @@
|
||||
"""Support for Aussie Broadband metric sensors."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import DATA_KILOBYTES, DATA_MEGABYTES, TIME_DAYS
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, SERVICE_ID
|
||||
|
||||
SENSOR_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
# Internet Services sensors
|
||||
SensorEntityDescription(
|
||||
key="usedMb",
|
||||
name="Data Used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:network",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="downloadedMb",
|
||||
name="Downloaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:download-network",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="uploadedMb",
|
||||
name="Uploaded",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=DATA_MEGABYTES,
|
||||
icon="mdi:upload-network",
|
||||
),
|
||||
# Mobile Phone Services sensors
|
||||
SensorEntityDescription(
|
||||
key="national",
|
||||
name="National Calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="mobile",
|
||||
name="Mobile Calls",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="international",
|
||||
name="International Calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone-plus",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="sms",
|
||||
name="SMS Sent",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:message-processing",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="internet",
|
||||
name="Data Used",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=DATA_KILOBYTES,
|
||||
icon="mdi:network",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="voicemail",
|
||||
name="Voicemail Calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="other",
|
||||
name="Other Calls",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
icon="mdi:phone",
|
||||
),
|
||||
# Generic sensors
|
||||
SensorEntityDescription(
|
||||
key="daysTotal",
|
||||
name="Billing Cycle Length",
|
||||
native_unit_of_measurement=TIME_DAYS,
|
||||
icon="mdi:calendar-range",
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="daysRemaining",
|
||||
name="Billing Cycle Remaining",
|
||||
native_unit_of_measurement=TIME_DAYS,
|
||||
icon="mdi:calendar-clock",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
):
|
||||
"""Set up the Aussie Broadband sensor platform from a config entry."""
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
AussieBroadandSensorEntity(service, description)
|
||||
for service in hass.data[DOMAIN][entry.entry_id]["services"]
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if description.key in service["coordinator"].data
|
||||
]
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
class AussieBroadandSensorEntity(CoordinatorEntity, SensorEntity):
|
||||
"""Base class for Aussie Broadband metric sensors."""
|
||||
|
||||
def __init__(
|
||||
self, service: dict[str, Any], description: SensorEntityDescription
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(service["coordinator"])
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{service[SERVICE_ID]}:{description.key}"
|
||||
self._attr_name = f"{service['name']} {description.name}"
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
if self.entity_description.key == "internet":
|
||||
return self.coordinator.data[self.entity_description.key]["kbytes"]
|
||||
if self.entity_description.key in ("national", "mobile", "sms"):
|
||||
return self.coordinator.data[self.entity_description.key]["calls"]
|
||||
return self.coordinator.data[self.entity_description.key]
|
50
homeassistant/components/aussie_broadband/strings.json
Normal file
50
homeassistant/components/aussie_broadband/strings.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"title": "Select Services",
|
||||
"data": {
|
||||
"services": "Services"
|
||||
}
|
||||
},
|
||||
"reauth": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "Update password for {username}",
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"no_services_found": "No services were found for this account",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Select Services",
|
||||
"data": {
|
||||
"services": "Services"
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ FLOWS = [
|
||||
"august",
|
||||
"aurora",
|
||||
"aurora_abb_powerone",
|
||||
"aussie_broadband",
|
||||
"awair",
|
||||
"axis",
|
||||
"azure_devops",
|
||||
|
@ -1403,6 +1403,9 @@ pyatome==0.1.1
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.9.8
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.9
|
||||
|
||||
# homeassistant.components.balboa
|
||||
pybalboa==0.13
|
||||
|
||||
|
@ -877,6 +877,9 @@ pyatmo==6.2.2
|
||||
# homeassistant.components.apple_tv
|
||||
pyatv==0.9.8
|
||||
|
||||
# homeassistant.components.aussie_broadband
|
||||
pyaussiebb==0.0.9
|
||||
|
||||
# homeassistant.components.balboa
|
||||
pybalboa==0.13
|
||||
|
||||
|
1
tests/components/aussie_broadband/__init__.py
Normal file
1
tests/components/aussie_broadband/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
"""Tests for the Aussie Broadband integration."""
|
58
tests/components/aussie_broadband/common.py
Normal file
58
tests/components/aussie_broadband/common.py
Normal file
@ -0,0 +1,58 @@
|
||||
"""Aussie Broadband common helpers for tests."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.aussie_broadband.const import (
|
||||
CONF_SERVICES,
|
||||
DOMAIN as AUSSIE_BROADBAND_DOMAIN,
|
||||
)
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_USERNAME
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
FAKE_SERVICES = [
|
||||
{
|
||||
"service_id": "12345678",
|
||||
"description": "Fake ABB NBN Service",
|
||||
"type": "NBN",
|
||||
"name": "NBN",
|
||||
},
|
||||
{
|
||||
"service_id": "87654321",
|
||||
"description": "Fake ABB Mobile Service",
|
||||
"type": "PhoneMobile",
|
||||
"name": "Mobile",
|
||||
},
|
||||
]
|
||||
|
||||
FAKE_DATA = {
|
||||
CONF_USERNAME: "test-username",
|
||||
CONF_PASSWORD: "test-password",
|
||||
}
|
||||
|
||||
|
||||
async def setup_platform(hass, platforms=[], side_effect=None, usage={}):
|
||||
"""Set up the Aussie Broadband platform."""
|
||||
mock_entry = MockConfigEntry(
|
||||
domain=AUSSIE_BROADBAND_DOMAIN,
|
||||
data=FAKE_DATA,
|
||||
options={CONF_SERVICES: ["12345678", "87654321"], CONF_SCAN_INTERVAL: 30},
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.aussie_broadband.PLATFORMS", platforms), patch(
|
||||
"aussiebb.asyncio.AussieBB.__init__", return_value=None
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.login",
|
||||
return_value=True,
|
||||
side_effect=side_effect,
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services",
|
||||
return_value=FAKE_SERVICES,
|
||||
side_effect=side_effect,
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_usage", return_value=usage
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_entry
|
322
tests/components/aussie_broadband/test_config_flow.py
Normal file
322
tests/components/aussie_broadband/test_config_flow.py
Normal file
@ -0,0 +1,322 @@
|
||||
"""Test the Aussie Broadband config flow."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from aiohttp import ClientConnectionError
|
||||
from aussiebb.asyncio import AuthenticationException
|
||||
|
||||
from homeassistant import config_entries, setup
|
||||
from homeassistant.components.aussie_broadband.const import CONF_SERVICES, DOMAIN
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import (
|
||||
RESULT_TYPE_ABORT,
|
||||
RESULT_TYPE_CREATE_ENTRY,
|
||||
RESULT_TYPE_FORM,
|
||||
)
|
||||
|
||||
from .common import FAKE_DATA, FAKE_SERVICES, setup_platform
|
||||
|
||||
TEST_USERNAME = FAKE_DATA[CONF_USERNAME]
|
||||
TEST_PASSWORD = FAKE_DATA[CONF_PASSWORD]
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant) -> None:
|
||||
"""Test we get the form."""
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result1["type"] == RESULT_TYPE_FORM
|
||||
assert result1["errors"] is None
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]
|
||||
), patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == TEST_USERNAME
|
||||
assert result2["data"] == FAKE_DATA
|
||||
assert result2["options"] == {CONF_SERVICES: ["12345678"]}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_already_configured(hass: HomeAssistant) -> None:
|
||||
"""Test already configured."""
|
||||
# Setup an entry
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]
|
||||
), patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Test Already configured
|
||||
result3 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]
|
||||
), patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result4["type"] == RESULT_TYPE_ABORT
|
||||
assert len(mock_setup_entry.mock_calls) == 0
|
||||
|
||||
|
||||
async def test_no_services(hass: HomeAssistant) -> None:
|
||||
"""Test when there are no services."""
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result1["type"] == RESULT_TYPE_FORM
|
||||
assert result1["errors"] is None
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[]), patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_ABORT
|
||||
assert result2["reason"] == "no_services_found"
|
||||
assert len(mock_setup_entry.mock_calls) == 0
|
||||
|
||||
|
||||
async def test_form_multiple_services(hass: HomeAssistant) -> None:
|
||||
"""Test the config flow with multiple services."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == RESULT_TYPE_FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch("aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
assert result2["step_id"] == "service"
|
||||
assert result2["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result3 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_SERVICES: [FAKE_SERVICES[1]["service_id"]]},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result3["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result3["title"] == TEST_USERNAME
|
||||
assert result3["data"] == FAKE_DATA
|
||||
assert result3["options"] == {
|
||||
CONF_SERVICES: [FAKE_SERVICES[1]["service_id"]],
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||
"""Test invalid auth is handled."""
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException()
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
assert result2["errors"] == {"base": "invalid_auth"}
|
||||
|
||||
|
||||
async def test_form_network_issue(hass: HomeAssistant) -> None:
|
||||
"""Test network issues are handled."""
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", side_effect=ClientConnectionError()
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
FAKE_DATA,
|
||||
)
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_FORM
|
||||
assert result2["errors"] == {"base": "cannot_connect"}
|
||||
|
||||
|
||||
async def test_reauth(hass: HomeAssistant) -> None:
|
||||
"""Test reauth flow."""
|
||||
|
||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||
|
||||
# Test reauth but the entry doesn't exist
|
||||
result1 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_REAUTH}, data=FAKE_DATA
|
||||
)
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]
|
||||
), patch(
|
||||
"homeassistant.components.aussie_broadband.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result1["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: TEST_PASSWORD,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["title"] == TEST_USERNAME
|
||||
assert result2["data"] == FAKE_DATA
|
||||
|
||||
# Test failed reauth
|
||||
result5 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_REAUTH},
|
||||
data=FAKE_DATA,
|
||||
)
|
||||
assert result5["step_id"] == "reauth"
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", side_effect=AuthenticationException()
|
||||
), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]):
|
||||
|
||||
result6 = await hass.config_entries.flow.async_configure(
|
||||
result5["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: "test-wrongpassword",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result6["step_id"] == "reauth"
|
||||
|
||||
# Test successful reauth
|
||||
with patch("aussiebb.asyncio.AussieBB.__init__", return_value=None), patch(
|
||||
"aussiebb.asyncio.AussieBB.login", return_value=True
|
||||
), patch("aussiebb.asyncio.AussieBB.get_services", return_value=[FAKE_SERVICES[0]]):
|
||||
|
||||
result7 = await hass.config_entries.flow.async_configure(
|
||||
result6["flow_id"],
|
||||
{
|
||||
CONF_PASSWORD: "test-newpassword",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result7["type"] == "abort"
|
||||
assert result7["reason"] == "reauth_successful"
|
||||
|
||||
|
||||
async def test_options_flow(hass):
|
||||
"""Test options flow."""
|
||||
entry = await setup_platform(hass)
|
||||
|
||||
with patch("aussiebb.asyncio.AussieBB.get_services", return_value=FAKE_SERVICES):
|
||||
|
||||
result1 = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
assert result1["type"] == RESULT_TYPE_FORM
|
||||
assert result1["step_id"] == "init"
|
||||
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result1["flow_id"],
|
||||
user_input={CONF_SERVICES: []},
|
||||
)
|
||||
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||
assert entry.options == {CONF_SERVICES: []}
|
||||
|
||||
|
||||
async def test_options_flow_auth_failure(hass):
|
||||
"""Test options flow with auth failure."""
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
|
||||
with patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", side_effect=AuthenticationException()
|
||||
):
|
||||
|
||||
result1 = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
assert result1["type"] == RESULT_TYPE_ABORT
|
||||
assert result1["reason"] == "invalid_auth"
|
||||
|
||||
|
||||
async def test_options_flow_network_failure(hass):
|
||||
"""Test options flow with connectivity failure."""
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
|
||||
with patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", side_effect=ClientConnectionError()
|
||||
):
|
||||
|
||||
result1 = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
assert result1["type"] == RESULT_TYPE_ABORT
|
||||
assert result1["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_options_flow_not_loaded(hass):
|
||||
"""Test the options flow aborts when the entry has unloaded due to a reauth."""
|
||||
|
||||
entry = await setup_platform(hass)
|
||||
|
||||
with patch(
|
||||
"aussiebb.asyncio.AussieBB.get_services", side_effect=AuthenticationException()
|
||||
):
|
||||
entry.state = config_entries.ConfigEntryState.NOT_LOADED
|
||||
result1 = await hass.config_entries.options.async_init(entry.entry_id)
|
||||
assert result1["type"] == RESULT_TYPE_ABORT
|
||||
assert result1["reason"] == "unknown"
|
35
tests/components/aussie_broadband/test_init.py
Normal file
35
tests/components/aussie_broadband/test_init.py
Normal file
@ -0,0 +1,35 @@
|
||||
"""Test the Aussie Broadband init."""
|
||||
from unittest.mock import patch
|
||||
|
||||
from aiohttp import ClientConnectionError
|
||||
from aussiebb.asyncio import AuthenticationException
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .common import setup_platform
|
||||
|
||||
|
||||
async def test_unload(hass: HomeAssistant) -> None:
|
||||
"""Test unload."""
|
||||
entry = await setup_platform(hass)
|
||||
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_auth_failure(hass: HomeAssistant) -> None:
|
||||
"""Test init with an authentication failure."""
|
||||
with patch(
|
||||
"homeassistant.components.aussie_broadband.config_flow.ConfigFlow.async_step_reauth",
|
||||
return_value={"type": data_entry_flow.RESULT_TYPE_FORM},
|
||||
) as mock_async_step_reauth:
|
||||
await setup_platform(hass, side_effect=AuthenticationException())
|
||||
mock_async_step_reauth.assert_called_once()
|
||||
|
||||
|
||||
async def test_net_failure(hass: HomeAssistant) -> None:
|
||||
"""Test init with a network failure."""
|
||||
entry = await setup_platform(hass, side_effect=ClientConnectionError())
|
||||
assert entry.state is ConfigEntryState.SETUP_RETRY
|
50
tests/components/aussie_broadband/test_sensor.py
Normal file
50
tests/components/aussie_broadband/test_sensor.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""Aussie Broadband sensor platform tests."""
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
|
||||
from .common import setup_platform
|
||||
|
||||
MOCK_NBN_USAGE = {
|
||||
"usedMb": 54321,
|
||||
"downloadedMb": 50000,
|
||||
"uploadedMb": 4321,
|
||||
"daysTotal": 28,
|
||||
"daysRemaining": 25,
|
||||
}
|
||||
|
||||
MOCK_MOBILE_USAGE = {
|
||||
"national": {"calls": 1, "cost": 0},
|
||||
"mobile": {"calls": 2, "cost": 0},
|
||||
"international": {"calls": 3, "cost": 0},
|
||||
"sms": {"calls": 4, "cost": 0},
|
||||
"internet": {"kbytes": 512, "cost": 0},
|
||||
"voicemail": {"calls": 6, "cost": 0},
|
||||
"other": {"calls": 7, "cost": 0},
|
||||
"daysTotal": 31,
|
||||
"daysRemaining": 30,
|
||||
"historical": [],
|
||||
}
|
||||
|
||||
|
||||
async def test_nbn_sensor_states(hass):
|
||||
"""Tests that the sensors are correct."""
|
||||
|
||||
await setup_platform(hass, [SENSOR_DOMAIN], usage=MOCK_NBN_USAGE)
|
||||
|
||||
assert hass.states.get("sensor.nbn_data_used").state == "54321"
|
||||
assert hass.states.get("sensor.nbn_downloaded").state == "50000"
|
||||
assert hass.states.get("sensor.nbn_uploaded").state == "4321"
|
||||
assert hass.states.get("sensor.nbn_billing_cycle_length").state == "28"
|
||||
assert hass.states.get("sensor.nbn_billing_cycle_remaining").state == "25"
|
||||
|
||||
|
||||
async def test_phone_sensor_states(hass):
|
||||
"""Tests that the sensors are correct."""
|
||||
|
||||
await setup_platform(hass, [SENSOR_DOMAIN], usage=MOCK_MOBILE_USAGE)
|
||||
|
||||
assert hass.states.get("sensor.mobile_national_calls").state == "1"
|
||||
assert hass.states.get("sensor.mobile_mobile_calls").state == "2"
|
||||
assert hass.states.get("sensor.mobile_sms_sent").state == "4"
|
||||
assert hass.states.get("sensor.mobile_data_used").state == "512"
|
||||
assert hass.states.get("sensor.mobile_billing_cycle_length").state == "31"
|
||||
assert hass.states.get("sensor.mobile_billing_cycle_remaining").state == "30"
|
Loading…
x
Reference in New Issue
Block a user