Add Aussie Broadband integration (#53552)

This commit is contained in:
Nick Whyte 2022-01-21 16:34:25 +11:00 committed by GitHub
parent 1f00ded33a
commit 3dd7ec6856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 952 additions and 0 deletions

View File

@ -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

View 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

View 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),
}
),
)

View 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"

View 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"
}

View 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]

View 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%]"
}
}
}

View File

@ -34,6 +34,7 @@ FLOWS = [
"august",
"aurora",
"aurora_abb_powerone",
"aussie_broadband",
"awair",
"axis",
"azure_devops",

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
"""Tests for the Aussie Broadband integration."""

View 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

View 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"

View 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

View 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"