Improve test coverage for nextcloud (#123148)

* add first data driven tests

* remove unused mock

* test unique_id migration

* test errors during setup

* test error during data update

* test update entity

* system_versionis always available

* make use of snapshot_platform helper

* use parametrize test for coordinator update errors

* apply suggestions

* don't touch internals on coordinator tests

* rework to use async_get_or_create instead of mock_registry
This commit is contained in:
Michael 2024-08-27 19:48:39 +02:00 committed by GitHub
parent e2d84f9a58
commit ea04269c49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 4855 additions and 40 deletions

View File

@ -32,12 +32,12 @@ class NextcloudUpdateSensor(NextcloudEntity, UpdateEntity):
"""Represents a Nextcloud update entity.""" """Represents a Nextcloud update entity."""
@property @property
def installed_version(self) -> str | None: def installed_version(self) -> str:
"""Version installed and in use.""" """Version installed and in use."""
return self.coordinator.data.get("system_version") return self.coordinator.data["system_version"]
@property @property
def latest_version(self) -> str | None: def latest_version(self) -> str:
"""Latest version available for install.""" """Latest version available for install."""
return self.coordinator.data.get( return self.coordinator.data.get(
"update_available_version", self.installed_version "update_available_version", self.installed_version
@ -46,7 +46,5 @@ class NextcloudUpdateSensor(NextcloudEntity, UpdateEntity):
@property @property
def release_url(self) -> str | None: def release_url(self) -> str | None:
"""URL to the full release notes of the latest version available.""" """URL to the full release notes of the latest version available."""
if self.latest_version: ver = "-".join(self.latest_version.split(".")[:3])
ver = "-".join(self.latest_version.split(".")[:3]) return f"https://nextcloud.com/changelog/#{ver}"
return f"https://nextcloud.com/changelog/#{ver}"
return None

View File

@ -1 +1,38 @@
"""Tests for the Nextcloud integration.""" """Tests for the Nextcloud integration."""
from unittest.mock import Mock, patch
from homeassistant.components.nextcloud.const import DOMAIN
from homeassistant.const import CONF_URL
from homeassistant.core import HomeAssistant
from .const import MOCKED_ENTRY_ID
from tests.common import MockConfigEntry
def mock_config_entry(config: dict) -> MockConfigEntry:
"""Return a mocked config entry."""
return MockConfigEntry(
domain=DOMAIN, title=config[CONF_URL], data=config, entry_id=MOCKED_ENTRY_ID
)
async def init_integration(
hass: HomeAssistant, config: dict, data: dict
) -> MockConfigEntry:
"""Set up the nextcloud integration."""
entry = mock_config_entry(config)
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.nextcloud.NextcloudMonitor",
) as mock_nextcloud_monitor,
):
mock_nextcloud_monitor.update = Mock(return_value=True)
mock_nextcloud_monitor.return_value.data = data
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry

View File

@ -1,19 +1,11 @@
"""Fixtrues for the Nextcloud integration tests.""" """Fixtrues for the Nextcloud integration tests."""
from collections.abc import Generator from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import AsyncMock, patch
import pytest import pytest
@pytest.fixture
def mock_nextcloud_monitor() -> Mock:
"""Mock of NextcloudMonitor."""
return Mock(
update=Mock(return_value=True),
)
@pytest.fixture @pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]: def mock_setup_entry() -> Generator[AsyncMock]:
"""Override async_setup_entry.""" """Override async_setup_entry."""

View File

@ -0,0 +1,182 @@
"""Constants for nextcloud tests."""
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
MOCKED_ENTRY_ID = "1234567890abcdef"
VALID_CONFIG = {
CONF_URL: "https://my.nc_url.local",
CONF_USERNAME: "nc_user",
CONF_PASSWORD: "nc_pass",
CONF_VERIFY_SSL: True,
}
NC_DATA = {
"nextcloud": {
"system": {
"version": "28.0.4.1",
"theme": "",
"enable_avatars": "yes",
"enable_previews": "yes",
"memcache.local": "\\OC\\Memcache\\APCu",
"memcache.distributed": "none",
"filelocking.enabled": "yes",
"memcache.locking": "none",
"debug": "no",
"freespace": 32769138688,
"cpuload": [2.06640625, 1.58447265625, 1.45263671875],
"mem_total": 30728192,
"mem_free": 6753280,
"swap_total": 10484736,
"swap_free": 10484736,
"apps": {
"num_installed": 41,
"num_updates_available": 0,
"app_updates": [],
},
"update": {"lastupdatedat": 1713048517, "available": False},
},
"storage": {
"num_users": 2,
"num_files": 6783,
"num_storages": 4,
"num_storages_local": 1,
"num_storages_home": 2,
"num_storages_other": 1,
},
"shares": {
"num_shares": 2,
"num_shares_user": 0,
"num_shares_groups": 0,
"num_shares_link": 2,
"num_shares_mail": 0,
"num_shares_room": 0,
"num_shares_link_no_password": 2,
"num_fed_shares_sent": 0,
"num_fed_shares_received": 1,
"permissions_3_17": 1,
"permissions_3_31": 1,
},
},
"server": {
"webserver": "Apache/2.4.57 (Debian)",
"php": {
"version": "8.2.18",
"memory_limit": 536870912,
"max_execution_time": 3600,
"upload_max_filesize": 536870912,
"opcache_revalidate_freq": 60,
"opcache": {
"opcache_enabled": True,
"cache_full": False,
"restart_pending": False,
"restart_in_progress": False,
"memory_usage": {
"used_memory": 72027112,
"free_memory": 62190616,
"wasted_memory": 0,
"current_wasted_percentage": 0,
},
"interned_strings_usage": {
"buffer_size": 33554432,
"used_memory": 12630360,
"free_memory": 20924072,
"number_of_strings": 69242,
},
"opcache_statistics": {
"num_cached_scripts": 1406,
"num_cached_keys": 2654,
"max_cached_keys": 16229,
"hits": 9739971,
"start_time": 1722222008,
"last_restart_time": 0,
"oom_restarts": 0,
"hash_restarts": 0,
"manual_restarts": 0,
"misses": 1406,
"blacklist_misses": 0,
"blacklist_miss_ratio": 0,
"opcache_hit_rate": 99.9855667222406,
},
"jit": {
"enabled": True,
"on": True,
"kind": 5,
"opt_level": 5,
"opt_flags": 6,
"buffer_size": 134217712,
"buffer_free": 133190688,
},
},
"apcu": {
"cache": {
"num_slots": 4099,
"ttl": 0,
"num_hits": 590911,
"num_misses": 55250,
"num_inserts": 55421,
"num_entries": 102,
"expunges": 0,
"start_time": 1722222008,
"mem_size": 175296,
"memory_type": "mmap",
},
"sma": {"num_seg": 1, "seg_size": 33554312, "avail_mem": 33342368},
},
"extensions": [
"Core",
"date",
"libxml",
"openssl",
"pcre",
"sqlite3",
"zlib",
"ctype",
"curl",
"dom",
"fileinfo",
"filter",
"hash",
"iconv",
"json",
"mbstring",
"SPL",
"session",
"PDO",
"pdo_sqlite",
"standard",
"posix",
"random",
"Reflection",
"Phar",
"SimpleXML",
"tokenizer",
"xml",
"xmlreader",
"xmlwriter",
"mysqlnd",
"apache2handler",
"apcu",
"bcmath",
"exif",
"ftp",
"gd",
"gmp",
"imagick",
"intl",
"ldap",
"memcached",
"pcntl",
"pdo_mysql",
"pdo_pgsql",
"redis",
"sodium",
"sysvsem",
"zip",
"Zend OPcache",
],
},
"database": {"type": "sqlite3", "version": "3.40.1", "size": "4784128"},
},
"activeUsers": {"last5minutes": 0, "last1hour": 0, "last24hours": 0},
}

View File

@ -0,0 +1,277 @@
# serializer version: 1
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_avatars_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_avatars_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Avatars enabled',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_system_enable_avatars',
'unique_id': '1234567890abcdef#system_enable_avatars',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_avatars_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local Avatars enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_avatars_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_debug_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_debug_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Debug enabled',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_system_debug',
'unique_id': '1234567890abcdef#system_debug',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_debug_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local Debug enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_debug_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_filelocking_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_filelocking_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Filelocking enabled',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_system_filelocking_enabled',
'unique_id': '1234567890abcdef#system_filelocking.enabled',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_filelocking_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local Filelocking enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_filelocking_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_jit_active-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_jit_active',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'JIT active',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_jit_on',
'unique_id': '1234567890abcdef#jit_on',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_jit_active-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local JIT active',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_jit_active',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_jit_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_jit_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'JIT enabled',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_jit_enabled',
'unique_id': '1234567890abcdef#jit_enabled',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_jit_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local JIT enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_jit_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_previews_enabled-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.my_nc_url_local_previews_enabled',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Previews enabled',
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'nextcloud_system_enable_previews',
'unique_id': '1234567890abcdef#system_enable_previews',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[binary_sensor.my_nc_url_local_previews_enabled-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'my.nc_url.local Previews enabled',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.my_nc_url_local_previews_enabled',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@ -2,7 +2,7 @@
# name: test_reauth # name: test_reauth
dict({ dict({
'password': 'other_password', 'password': 'other_password',
'url': 'nc_url', 'url': 'https://my.nc_url.local',
'username': 'other_user', 'username': 'other_user',
'verify_ssl': True, 'verify_ssl': True,
}) })
@ -10,7 +10,7 @@
# name: test_user_create_entry # name: test_user_create_entry
dict({ dict({
'password': 'nc_pass', 'password': 'nc_pass',
'url': 'nc_url', 'url': 'https://my.nc_url.local',
'username': 'nc_user', 'username': 'nc_user',
'verify_ssl': True, 'verify_ssl': True,
}) })

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
# serializer version: 1
# name: test_async_setup_entry[update.my_nc_url_local_none-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'update',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'update.my_nc_url_local_none',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'nextcloud',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': '1234567890abcdef#update',
'unit_of_measurement': None,
})
# ---
# name: test_async_setup_entry[update.my_nc_url_local_none-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'auto_update': False,
'entity_picture': 'https://brands.home-assistant.io/_/nextcloud/icon.png',
'friendly_name': 'my.nc_url.local None',
'in_progress': False,
'installed_version': '28.0.4.1',
'latest_version': '28.0.4.1',
'release_summary': None,
'release_url': 'https://nextcloud.com/changelog/#28-0-4',
'skipped_version': None,
'supported_features': <UpdateEntityFeature: 0>,
'title': None,
}),
'context': <ANY>,
'entity_id': 'update.my_nc_url_local_none',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,33 @@
"""Tests for the Nextcloud binary sensors."""
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import init_integration
from .const import NC_DATA, VALID_CONFIG
from tests.common import snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_async_setup_entry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test a successful setup entry."""
with patch(
"homeassistant.components.nextcloud.PLATFORMS", [Platform.BINARY_SENSOR]
):
entry = await init_integration(hass, VALID_CONFIG, NC_DATA)
states = hass.states.async_all()
assert len(states) == 6
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)

View File

@ -1,6 +1,6 @@
"""Tests for the Nextcloud config flow.""" """Tests for the Nextcloud config flow."""
from unittest.mock import Mock, patch from unittest.mock import patch
from nextcloudmonitor import ( from nextcloudmonitor import (
NextcloudMonitorAuthorizationError, NextcloudMonitorAuthorizationError,
@ -12,24 +12,19 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.nextcloud.const import DOMAIN from homeassistant.components.nextcloud.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from .const import VALID_CONFIG
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
pytestmark = pytest.mark.usefixtures("mock_setup_entry") pytestmark = pytest.mark.usefixtures("mock_setup_entry")
VALID_CONFIG = {
CONF_URL: "nc_url",
CONF_USERNAME: "nc_user",
CONF_PASSWORD: "nc_pass",
CONF_VERIFY_SSL: True,
}
async def test_user_create_entry( async def test_user_create_entry(
hass: HomeAssistant, mock_nextcloud_monitor: Mock, snapshot: SnapshotAssertion hass: HomeAssistant, snapshot: SnapshotAssertion
) -> None: ) -> None:
"""Test that the user step works.""" """Test that the user step works."""
# start user flow # start user flow
@ -85,7 +80,7 @@ async def test_user_create_entry(
# test success # test success
with patch( with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor", "homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
return_value=mock_nextcloud_monitor, return_value=True,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -94,17 +89,15 @@ async def test_user_create_entry(
await hass.async_block_till_done() await hass.async_block_till_done()
assert result["type"] is FlowResultType.CREATE_ENTRY assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "nc_url" assert result["title"] == "https://my.nc_url.local"
assert result["data"] == snapshot assert result["data"] == snapshot
async def test_user_already_configured( async def test_user_already_configured(hass: HomeAssistant) -> None:
hass: HomeAssistant, mock_nextcloud_monitor: Mock
) -> None:
"""Test that errors are shown when duplicates are added.""" """Test that errors are shown when duplicates are added."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title="nc_url", title="https://my.nc_url.local",
unique_id="nc_url", unique_id="nc_url",
data=VALID_CONFIG, data=VALID_CONFIG,
) )
@ -119,7 +112,7 @@ async def test_user_already_configured(
with patch( with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor", "homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
return_value=mock_nextcloud_monitor, return_value=True,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -131,13 +124,11 @@ async def test_user_already_configured(
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
async def test_reauth( async def test_reauth(hass: HomeAssistant, snapshot: SnapshotAssertion) -> None:
hass: HomeAssistant, mock_nextcloud_monitor: Mock, snapshot: SnapshotAssertion
) -> None:
"""Test that the re-auth flow works.""" """Test that the re-auth flow works."""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
title="nc_url", title="https://my.nc_url.local",
unique_id="nc_url", unique_id="nc_url",
data=VALID_CONFIG, data=VALID_CONFIG,
) )
@ -206,7 +197,7 @@ async def test_reauth(
# test success # test success
with patch( with patch(
"homeassistant.components.nextcloud.config_flow.NextcloudMonitor", "homeassistant.components.nextcloud.config_flow.NextcloudMonitor",
return_value=mock_nextcloud_monitor, return_value=True,
): ):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],

View File

@ -0,0 +1,69 @@
"""Tests for the Nextcloud coordinator."""
from unittest.mock import Mock, patch
from freezegun.api import FrozenDateTimeFactory
from nextcloudmonitor import (
NextcloudMonitor,
NextcloudMonitorAuthorizationError,
NextcloudMonitorConnectionError,
NextcloudMonitorError,
NextcloudMonitorRequestError,
)
import pytest
from homeassistant.components.nextcloud.const import DEFAULT_SCAN_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from . import mock_config_entry
from .const import NC_DATA, VALID_CONFIG
from tests.common import async_fire_time_changed
@pytest.mark.parametrize(
("error"),
[
(NextcloudMonitorAuthorizationError),
(NextcloudMonitorConnectionError),
(NextcloudMonitorRequestError),
],
)
async def test_data_update(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, error: NextcloudMonitorError
) -> None:
"""Test a coordinator data updates."""
entry = mock_config_entry(VALID_CONFIG)
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.nextcloud.NextcloudMonitor", spec=NextcloudMonitor
) as mock_nextcloud_monitor,
):
mock_nextcloud_monitor.return_value.update = Mock(
return_value=True,
side_effect=[None, error, None],
)
mock_nextcloud_monitor.return_value.data = NC_DATA
assert await hass.config_entries.async_setup(entry.entry_id)
# Test successful setup and first data fetch
await hass.async_block_till_done(wait_background_tasks=True)
states = hass.states.async_all()
assert (state != STATE_UNAVAILABLE for state in states)
# Test states get unavailable on error
freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
states = hass.states.async_all()
assert (state == STATE_UNAVAILABLE for state in states)
# Test successful data fetch
freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
states = hass.states.async_all()
assert (state != STATE_UNAVAILABLE for state in states)

View File

@ -0,0 +1,95 @@
"""Tests for the Nextcloud init."""
from unittest.mock import Mock, patch
from nextcloudmonitor import (
NextcloudMonitorAuthorizationError,
NextcloudMonitorConnectionError,
NextcloudMonitorError,
NextcloudMonitorRequestError,
)
import pytest
from homeassistant.components.nextcloud.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_URL, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import init_integration, mock_config_entry
from .const import MOCKED_ENTRY_ID, NC_DATA, VALID_CONFIG
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_async_setup_entry(
hass: HomeAssistant,
) -> None:
"""Test a successful setup entry."""
assert await init_integration(hass, VALID_CONFIG, NC_DATA)
async def test_unique_id_migration(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
) -> None:
"""Test migration of unique ids to stable ones."""
object_id = "my_nc_url_local_system_version"
entity_id = f"{Platform.SENSOR}.{object_id}"
entry = mock_config_entry(VALID_CONFIG)
entry.add_to_hass(hass)
entity = entity_registry.async_get_or_create(
Platform.SENSOR,
DOMAIN,
f"{VALID_CONFIG[CONF_URL]}#nextcloud_system_version",
suggested_object_id=object_id,
config_entry=entry,
)
# test old unique id
assert entity.entity_id == entity_id
assert entity.unique_id == f"{VALID_CONFIG[CONF_URL]}#nextcloud_system_version"
with (
patch(
"homeassistant.components.nextcloud.NextcloudMonitor"
) as mock_nextcloud_monitor,
):
mock_nextcloud_monitor.update = Mock(return_value=True)
mock_nextcloud_monitor.return_value.data = NC_DATA
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
# test migrated unique id
reg_entry = entity_registry.async_get(entity_id)
assert reg_entry.unique_id == f"{MOCKED_ENTRY_ID}#system_version"
@pytest.mark.parametrize(
("exception", "expcted_entry_state"),
[
(NextcloudMonitorAuthorizationError, ConfigEntryState.SETUP_ERROR),
(NextcloudMonitorConnectionError, ConfigEntryState.SETUP_RETRY),
(NextcloudMonitorRequestError, ConfigEntryState.SETUP_RETRY),
],
)
async def test_setup_entry_errors(
hass: HomeAssistant,
exception: NextcloudMonitorError,
expcted_entry_state: ConfigEntryState,
) -> None:
"""Test a successful setup entry."""
entry = mock_config_entry(VALID_CONFIG)
entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.nextcloud.NextcloudMonitor", side_effect=exception
),
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state == expcted_entry_state

View File

@ -0,0 +1,31 @@
"""Tests for the Nextcloud sensors."""
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import init_integration
from .const import NC_DATA, VALID_CONFIG
from tests.common import snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_async_setup_entry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test a successful setup entry."""
with patch("homeassistant.components.nextcloud.PLATFORMS", [Platform.SENSOR]):
entry = await init_integration(hass, VALID_CONFIG, NC_DATA)
states = hass.states.async_all()
assert len(states) == 80
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)

View File

@ -0,0 +1,80 @@
"""Tests for the Nextcloud update entity."""
from copy import deepcopy
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.const import STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import init_integration
from .const import NC_DATA, VALID_CONFIG
from tests.common import snapshot_platform
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_async_setup_entry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
) -> None:
"""Test a successful setup entry."""
with patch("homeassistant.components.nextcloud.PLATFORMS", [Platform.UPDATE]):
entry = await init_integration(hass, VALID_CONFIG, NC_DATA)
states = hass.states.async_all()
assert len(states) == 1
await snapshot_platform(hass, entity_registry, snapshot, entry.entry_id)
async def test_setup_entity_without_update(
hass: HomeAssistant, snapshot: SnapshotAssertion
) -> None:
"""Test update entity is created w/o available update."""
with patch("homeassistant.components.nextcloud.PLATFORMS", [Platform.UPDATE]):
await init_integration(hass, VALID_CONFIG, NC_DATA)
states = hass.states.async_all()
assert len(states) == 1
assert states[0].state == STATE_OFF
assert states[0].attributes["installed_version"] == "28.0.4.1"
assert states[0].attributes["latest_version"] == "28.0.4.1"
assert (
states[0].attributes["release_url"] == "https://nextcloud.com/changelog/#28-0-4"
)
async def test_setup_entity_with_update(
hass: HomeAssistant, snapshot: SnapshotAssertion
) -> None:
"""Test update entity is created with available update."""
data = deepcopy(NC_DATA)
data["nextcloud"]["system"]["update"]["available"] = True
data["nextcloud"]["system"]["update"]["available_version"] = "30.0.0.0"
with patch("homeassistant.components.nextcloud.PLATFORMS", [Platform.UPDATE]):
await init_integration(hass, VALID_CONFIG, data)
states = hass.states.async_all()
assert len(states) == 1
assert states[0].state == STATE_ON
assert states[0].attributes["installed_version"] == "28.0.4.1"
assert states[0].attributes["latest_version"] == "30.0.0.0"
assert (
states[0].attributes["release_url"] == "https://nextcloud.com/changelog/#30-0-0"
)
async def test_setup_no_entity(hass: HomeAssistant) -> None:
"""Test no update entity is created, when no data available."""
data = deepcopy(NC_DATA)
data["nextcloud"]["system"].pop("update") # only nc<28.0.0
with patch("homeassistant.components.nextcloud.PLATFORMS", [Platform.UPDATE]):
await init_integration(hass, VALID_CONFIG, data)
states = hass.states.async_all()
assert len(states) == 0