Add tests to Lidarr (#79610)

* Add tests to Lidarr

* fix js files

* take out the trash

* fix 3.9

* uno mas

* fix fixture

* ruff

* Update const.py

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Robert Hillis 2023-03-28 10:49:32 -04:00 committed by GitHub
parent a26d95ec02
commit 866518c5a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 518 additions and 141 deletions

View File

@ -629,9 +629,6 @@ omit =
homeassistant/components/lg_netcast/media_player.py
homeassistant/components/lg_soundbar/__init__.py
homeassistant/components/lg_soundbar/media_player.py
homeassistant/components/lidarr/__init__.py
homeassistant/components/lidarr/coordinator.py
homeassistant/components/lidarr/sensor.py
homeassistant/components/life360/__init__.py
homeassistant/components/life360/coordinator.py
homeassistant/components/life360/device_tracker.py

View File

@ -4,3 +4,5 @@ azure-*.yml
docs/source/_templates/*
homeassistant/components/*/translations/*.json
homeassistant/generated/*
tests/components/lidarr/fixtures/initialize.js
tests/components/lidarr/fixtures/initialize-wrong.js

View File

@ -17,8 +17,6 @@ BYTE_SIZES = [
]
# Defaults
DEFAULT_DAYS = "1"
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "Lidarr"
DEFAULT_UNIT = UnitOfInformation.GIGABYTES
DEFAULT_MAX_RECORDS = 20

View File

@ -1,53 +1 @@
"""Tests for the Lidarr component."""
from aiopyarr.lidarr_client import LidarrClient
from homeassistant.components.lidarr.const import DOMAIN
from homeassistant.const import (
CONF_API_KEY,
CONF_URL,
CONF_VERIFY_SSL,
CONTENT_TYPE_JSON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
BASE_PATH = ""
API_KEY = "1234567890abcdef1234567890abcdef"
URL = "http://127.0.0.1:8686"
client = LidarrClient(session=async_get_clientsession, api_token=API_KEY, url=URL)
API_URL = f"{URL}/api/{client._host.api_ver}"
MOCK_REAUTH_INPUT = {CONF_API_KEY: "new_key"}
MOCK_USER_INPUT = {
CONF_URL: URL,
CONF_VERIFY_SSL: False,
}
CONF_DATA = MOCK_USER_INPUT | {CONF_API_KEY: API_KEY}
def mock_connection(
aioclient_mock: AiohttpClientMocker,
url: str = API_URL,
) -> None:
"""Mock lidarr connection."""
aioclient_mock.get(
f"{url}/system/status",
text=load_fixture("lidarr/system-status.json"),
headers={"Content-Type": CONTENT_TYPE_JSON},
)
def create_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Create Efergy entry in Home Assistant."""
entry = MockConfigEntry(
domain=DOMAIN,
data=CONF_DATA,
)
entry.add_to_hass(hass)
return entry

View File

@ -0,0 +1,142 @@
"""Configure pytest for Lidarr tests."""
from __future__ import annotations
from collections.abc import Awaitable, Callable, Generator
from http import HTTPStatus
from aiohttp.client_exceptions import ClientError
from aiopyarr.lidarr_client import LidarrClient
import pytest
from homeassistant.components.lidarr.const import DOMAIN
from homeassistant.const import (
CONF_API_KEY,
CONF_URL,
CONF_VERIFY_SSL,
CONTENT_TYPE_JSON,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, load_fixture
from tests.test_util.aiohttp import AiohttpClientMocker
URL = "http://127.0.0.1:8668"
API_KEY = "1234567890abcdef1234567890abcdef"
client = LidarrClient(session=async_get_clientsession, api_token=API_KEY, url=URL)
API_URL = f"{URL}/api/{client._host.api_ver}"
MOCK_INPUT = {CONF_URL: URL, CONF_VERIFY_SSL: False}
CONF_DATA = MOCK_INPUT | {CONF_API_KEY: API_KEY}
ComponentSetup = Callable[[], Awaitable[None]]
def mock_error(
aioclient_mock: AiohttpClientMocker, status: HTTPStatus | None = None
) -> None:
"""Mock an error."""
if status:
aioclient_mock.get(f"{API_URL}/queue", status=status)
aioclient_mock.get(f"{API_URL}/rootfolder", status=status)
aioclient_mock.get(f"{API_URL}/system/status", status=status)
aioclient_mock.get(f"{API_URL}/wanted/missing", status=status)
aioclient_mock.get(f"{API_URL}/queue", exc=ClientError)
aioclient_mock.get(f"{API_URL}/rootfolder", exc=ClientError)
aioclient_mock.get(f"{API_URL}/system/status", exc=ClientError)
aioclient_mock.get(f"{API_URL}/wanted/missing", exc=ClientError)
@pytest.fixture
def cannot_connect(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock cannot connect error."""
mock_error(aioclient_mock, status=HTTPStatus.INTERNAL_SERVER_ERROR)
@pytest.fixture
def invalid_auth(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock invalid authorization error."""
mock_error(aioclient_mock, status=HTTPStatus.UNAUTHORIZED)
@pytest.fixture
def wrong_app(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock Lidarr wrong app."""
aioclient_mock.get(
f"{URL}/initialize.js",
text=load_fixture("lidarr/initialize-wrong.js"),
headers={"Content-Type": "application/javascript"},
)
@pytest.fixture
def zeroconf_failed(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock Lidarr zero configuration failure."""
aioclient_mock.get(
f"{URL}/initialize.js",
text="login-failed",
headers={"Content-Type": "application/javascript"},
)
@pytest.fixture
def unknown(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock Lidarr unknown error."""
aioclient_mock.get(
f"{URL}/initialize.js",
text="something went wrong",
headers={"Content-Type": "application/javascript"},
)
@pytest.fixture(name="connection")
def mock_connection(aioclient_mock: AiohttpClientMocker) -> None:
"""Mock Lidarr connection."""
aioclient_mock.get(
f"{URL}/initialize.js",
text=load_fixture("lidarr/initialize.js"),
headers={"Content-Type": "application/javascript"},
)
aioclient_mock.get(
f"{API_URL}/system/status",
text=load_fixture("lidarr/system-status.json"),
headers={"Content-Type": CONTENT_TYPE_JSON},
)
aioclient_mock.get(
f"{API_URL}/queue",
text=load_fixture("lidarr/queue.json"),
headers={"Content-Type": CONTENT_TYPE_JSON},
)
aioclient_mock.get(
f"{API_URL}/wanted/missing",
text=load_fixture("lidarr/wanted-missing.json"),
headers={"Content-Type": CONTENT_TYPE_JSON},
)
aioclient_mock.get(
f"{API_URL}/rootfolder",
text=load_fixture("lidarr/rootfolder-linux.json"),
headers={"Content-Type": CONTENT_TYPE_JSON},
)
@pytest.fixture(name="config_entry")
def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Create Lidarr entry in Home Assistant."""
return MockConfigEntry(domain=DOMAIN, data=CONF_DATA)
@pytest.fixture(name="setup_integration")
async def mock_setup_integration(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> Generator[ComponentSetup, None, None]:
"""Set up the lidarr integration in Home Assistant."""
config_entry.add_to_hass(hass)
async def func() -> None:
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
return func

View File

@ -0,0 +1,12 @@
window.Radarr = {
apiRoot: '/api/v3',
apiKey: '1234567890abcdef1234567890abcdef',
release: '4.0.3.5849-develop',
version: '4.0.3.5849',
instanceName: 'Radarr',
branch: 'nightly',
analytics: true,
userHash: 'abcd1234',
urlBase: '',
isProduction: true
};

View File

@ -0,0 +1,12 @@
window.Lidarr = {
apiRoot: '/api/v1',
apiKey: '1234567890abcdef1234567890abcdef',
release: '10.0.0.34882-develop',
version: '10.0.0.34882',
instanceName: 'Lidarr',
branch: 'nightly',
analytics: true,
userHash: 'abcd1234',
urlBase: '',
isProduction: true
};

View File

@ -0,0 +1,57 @@
{
"page": 1,
"pageSize": 20,
"sortKey": "timeleft",
"sortDirection": "default",
"totalRecords": 2,
"records": [
{
"artistId": 1,
"albumId": 1,
"quality": {
"quality": { "id": 0, "name": "Unknown" },
"revision": { "version": 1, "real": 0, "isRepack": false }
},
"size": 1000000,
"title": "string",
"sizeleft": 100000,
"timeleft": "00:00:00",
"estimatedCompletionTime": "2020-09-26T18:47:46Z",
"status": "downloading",
"trackedDownloadStatus": "ok",
"trackedDownloadState": "downloading",
"statusMessages": [],
"downloadId": "string",
"protocol": "string",
"downloadClient": "testclient",
"indexer": "test",
"outputPath": "/downloads/string",
"downloadForced": false,
"id": 1
},
{
"artistId": 1,
"albumId": 1,
"quality": {
"quality": { "id": 0, "name": "Unknown" },
"revision": { "version": 1, "real": 0, "isRepack": false }
},
"size": 2000000,
"title": "string2",
"sizeleft": 1000000,
"timeleft": "00:00:10",
"estimatedCompletionTime": "2020-09-26T18:47:46Z",
"status": "downloading",
"trackedDownloadStatus": "ok",
"trackedDownloadState": "downloading",
"statusMessages": [],
"downloadId": "string",
"protocol": "string",
"downloadClient": "testclient",
"indexer": "test",
"outputPath": "/downloads/string",
"downloadForced": false,
"id": 1
}
]
}

View File

@ -0,0 +1,15 @@
[
{
"name": "/music/",
"path": "/music/",
"defaultMetadataProfileId": 1,
"defaultQualityProfileId": 1,
"defaultMonitorOption": "all",
"defaultNewItemMonitorOption": "all",
"defaultTags": [],
"accessible": true,
"freeSpace": 1000000000,
"totalSpace": 100000000000,
"id": 2
}
]

View File

@ -5,8 +5,8 @@
"isProduction": false,
"isAdmin": false,
"isUserInteractive": true,
"startupPath": "C:\\ProgramData\\Radarr",
"appData": "C:\\ProgramData\\Radarr",
"startupPath": "C:\\ProgramData\\Lidarr",
"appData": "C:\\ProgramData\\Lidarr",
"osName": "Windows",
"osVersion": "10.0.18363.0",
"isNetCore": true,

View File

@ -0,0 +1,134 @@
{
"page": 1,
"pageSize": 20,
"sortKey": "title",
"sortDirection": "default",
"totalRecords": 1,
"records": [
{
"title": "test",
"disambiguation": "string",
"overview": "string",
"artistId": 0,
"foreignAlbumId": "string",
"monitored": true,
"anyReleaseOk": true,
"profileId": 1,
"duration": 0,
"albumType": "Album",
"secondaryTypes": [
{
"id": 0,
"name": "string"
}
],
"mediumCount": 1,
"ratings": {
"votes": 0,
"value": 0
},
"releaseDate": "1968-01-01T00:00:00Z",
"releases": [
{
"id": 0,
"albumId": 0,
"foreignReleaseId": "string",
"title": "string",
"status": "string",
"duration": 0,
"trackCount": 1,
"media": [
{
"mediumNumber": 1,
"mediumName": "Unknown",
"mediumFormat": "Unknown"
}
],
"mediumCount": 1,
"disambiguation": "",
"country": ["string"],
"label": ["test"],
"format": "Unknown",
"monitored": true
}
],
"genres": ["string"],
"media": [
{
"mediumNumber": 1,
"mediumName": "Unknown",
"mediumFormat": "Unknown"
}
],
"artist": {
"artistMetadataId": 0,
"status": "continuing",
"ended": false,
"artistName": "test",
"foreignArtistId": "string",
"tadbId": 0,
"discogsId": 0,
"overview": "string",
"artistType": "Group",
"disambiguation": "",
"links": [
{
"url": "string",
"name": "string"
}
],
"images": [
{
"url": "https://test.jpg",
"coverType": "fanart",
"extension": ".jpg"
}
],
"path": "string",
"qualityProfileId": 1,
"metadataProfileId": 1,
"monitored": true,
"monitorNewItems": "all",
"genres": ["string"],
"cleanName": "string",
"sortName": "string",
"tags": [0],
"added": "2020-03-18T15:51:22Z",
"ratings": {
"votes": 0,
"value": 0
},
"statistics": {
"albumCount": 0,
"trackFileCount": 0,
"trackCount": 0,
"totalTrackCount": 0,
"sizeOnDisk": 0,
"percentOfTracks": 0
},
"id": 0
},
"images": [
{
"url": "string",
"coverType": "poster"
}
],
"links": [
{
"url": "string",
"name": "string"
}
],
"statistics": {
"trackFileCount": 0,
"trackCount": 1,
"totalTrackCount": 1,
"sizeOnDisk": 0,
"percentOfTracks": 0
},
"grabbed": false,
"id": 1
}
]
}

View File

@ -1,138 +1,104 @@
"""Test Lidarr config flow."""
from unittest.mock import patch
from aiopyarr import exceptions
from homeassistant import data_entry_flow
from homeassistant.components.lidarr.const import DEFAULT_NAME, DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
from homeassistant.const import CONF_API_KEY, CONF_SOURCE
from homeassistant.core import HomeAssistant
from . import API_KEY, CONF_DATA, MOCK_USER_INPUT, create_entry, mock_connection
from tests.test_util.aiohttp import AiohttpClientMocker
from .conftest import CONF_DATA, MOCK_INPUT, ComponentSetup
def _patch_client():
return patch(
"homeassistant.components.lidarr.config_flow.LidarrClient.async_get_system_status"
)
async def test_flow_user_form(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
async def test_flow_user_form(hass: HomeAssistant, connection) -> None:
"""Test that the user set up form is served."""
mock_connection(aioclient_mock)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
with patch(
"homeassistant.components.lidarr.config_flow.LidarrClient.async_try_zeroconf",
return_value=("/api/v3", API_KEY, ""),
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_USER_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == DEFAULT_NAME
assert result["data"] == CONF_DATA
async def test_flow_user_invalid_auth(hass: HomeAssistant) -> None:
async def test_flow_user_invalid_auth(hass: HomeAssistant, invalid_auth) -> None:
"""Test invalid authentication."""
with _patch_client() as client:
client.side_effect = exceptions.ArrAuthenticationException
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_DATA,
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=CONF_DATA,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "invalid_auth"
async def test_flow_user_cannot_connect(hass: HomeAssistant) -> None:
async def test_flow_user_cannot_connect(hass: HomeAssistant, cannot_connect) -> None:
"""Test connection error."""
with _patch_client() as client:
client.side_effect = exceptions.ArrConnectionException
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_DATA,
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=CONF_DATA,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "cannot_connect"
async def test_wrong_app(hass: HomeAssistant) -> None:
async def test_wrong_app(hass: HomeAssistant, wrong_app) -> None:
"""Test we show user form on wrong app."""
with patch(
"homeassistant.components.lidarr.config_flow.LidarrClient.async_try_zeroconf",
side_effect=exceptions.ArrWrongAppException,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=MOCK_USER_INPUT,
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=MOCK_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "wrong_app"
async def test_zero_conf_failure(hass: HomeAssistant) -> None:
"""Test we show user form on api key retrieval failure."""
with patch(
"homeassistant.components.lidarr.config_flow.LidarrClient.async_try_zeroconf",
side_effect=exceptions.ArrZeroConfException,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=MOCK_USER_INPUT,
)
async def test_zeroconf_failed(hass: HomeAssistant, zeroconf_failed) -> None:
"""Test we show user form on zeroconf failure."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
data=MOCK_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "zeroconf_failed"
async def test_flow_user_unknown_error(hass: HomeAssistant) -> None:
async def test_flow_user_unknown_error(hass: HomeAssistant, unknown) -> None:
"""Test unknown error."""
with _patch_client() as client:
client.side_effect = exceptions.ArrException
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_DATA,
)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={CONF_SOURCE: SOURCE_USER},
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=CONF_DATA,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"]["base"] == "unknown"
async def test_flow_reauth(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
hass: HomeAssistant, setup_integration: ComponentSetup, connection
) -> None:
"""Test reauth."""
entry = create_entry(hass)
mock_connection(aioclient_mock)
await setup_integration()
entry = hass.config_entries.async_entries(DOMAIN)[0]
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={

View File

@ -0,0 +1,61 @@
"""Test Lidarr integration."""
from homeassistant.components.lidarr.const import DEFAULT_NAME, DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .conftest import ComponentSetup
async def test_setup(
hass: HomeAssistant, setup_integration: ComponentSetup, connection
) -> None:
"""Test setup."""
await setup_integration()
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert entry.state == ConfigEntryState.LOADED
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.NOT_LOADED
assert not hass.data.get(DOMAIN)
async def test_async_setup_entry_not_ready(
hass: HomeAssistant, setup_integration: ComponentSetup, cannot_connect
) -> None:
"""Test that it throws ConfigEntryNotReady when exception occurs during setup."""
await setup_integration()
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert entry.state == ConfigEntryState.SETUP_RETRY
assert not hass.data.get(DOMAIN)
async def test_async_setup_entry_auth_failed(
hass: HomeAssistant, setup_integration: ComponentSetup, invalid_auth
) -> None:
"""Test that it throws ConfigEntryAuthFailed when authentication fails."""
await setup_integration()
entry = hass.config_entries.async_entries(DOMAIN)[0]
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert entry.state == ConfigEntryState.SETUP_ERROR
assert not hass.data.get(DOMAIN)
async def test_device_info(
hass: HomeAssistant, setup_integration: ComponentSetup, connection
) -> None:
"""Test device info."""
await setup_integration()
entry = hass.config_entries.async_entries(DOMAIN)[0]
device_registry = dr.async_get(hass)
await hass.async_block_till_done()
device = device_registry.async_get_device({(DOMAIN, entry.entry_id)})
assert device.configuration_url == "http://127.0.0.1:8668"
assert device.identifiers == {(DOMAIN, entry.entry_id)}
assert device.manufacturer == DEFAULT_NAME
assert device.name == "Mock Title"
assert device.sw_version == "10.0.0.34882"

View File

@ -0,0 +1,33 @@
"""The tests for Lidarr sensor platform."""
from unittest.mock import AsyncMock
from homeassistant.components.sensor import CONF_STATE_CLASS, SensorStateClass
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT
from homeassistant.core import HomeAssistant
from .conftest import ComponentSetup
async def test_sensors(
hass: HomeAssistant,
setup_integration: ComponentSetup,
entity_registry_enabled_by_default: AsyncMock,
connection,
):
"""Test for successfully setting up the Lidarr platform."""
await setup_integration()
state = hass.states.get("sensor.mock_title_disk_space")
assert state.state == "0.93"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "GB"
state = hass.states.get("sensor.mock_title_queue")
assert state.state == "2"
assert state.attributes.get("string") == "stopped"
assert state.attributes.get("string2") == "downloading"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Albums"
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL
state = hass.states.get("sensor.mock_title_wanted")
assert state.state == "1"
assert state.attributes.get("test") == "test"
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "Albums"
assert state.attributes.get(CONF_STATE_CLASS) == SensorStateClass.TOTAL