mirror of
https://github.com/home-assistant/core.git
synced 2026-05-31 21:19:56 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1141ab2887 | |||
| d7c13fee27 | |||
| a0a44f7a25 |
@@ -40,6 +40,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
login_task: asyncio.Task | None = None
|
||||
refresh_token: str | None = None
|
||||
tado: Tado | None = None
|
||||
tado_device_url: str = ""
|
||||
user_code: str = ""
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
@@ -69,8 +71,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.exception("Error while initiating Tado")
|
||||
return self.async_abort(reason="cannot_connect")
|
||||
assert self.tado is not None
|
||||
tado_device_url = self.tado.device_verification_url()
|
||||
user_code = URL(tado_device_url).query["user_code"]
|
||||
self.tado_device_url = self.tado.device_verification_url()
|
||||
self.user_code = URL(self.tado_device_url).query["user_code"]
|
||||
|
||||
async def _wait_for_login() -> None:
|
||||
"""Wait for the user to login."""
|
||||
@@ -119,8 +121,8 @@ class TadoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="user",
|
||||
progress_action="wait_for_device",
|
||||
description_placeholders={
|
||||
"url": tado_device_url,
|
||||
"code": user_code,
|
||||
"url": self.tado_device_url,
|
||||
"code": self.user_code,
|
||||
},
|
||||
progress_task=self.login_task,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ PLATFORMS = [
|
||||
Platform.LIGHT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.UPDATE,
|
||||
]
|
||||
|
||||
TASMOTA_EVENT = "tasmota_event"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Data update coordinators for Tasmota."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from aiogithubapi import (
|
||||
GitHubAPI,
|
||||
GitHubConnectionException,
|
||||
GitHubException,
|
||||
GitHubRatelimitException,
|
||||
GitHubReleaseModel,
|
||||
)
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
|
||||
class TasmotaLatestReleaseUpdateCoordinator(DataUpdateCoordinator[GitHubReleaseModel]):
|
||||
"""Data update coordinator for Tasmota latest release info."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Initialize the coordinator."""
|
||||
self.client = GitHubAPI(session=async_get_clientsession(hass))
|
||||
super().__init__(
|
||||
hass,
|
||||
logger=logging.getLogger(__name__),
|
||||
config_entry=config_entry,
|
||||
name="Tasmota latest release",
|
||||
update_interval=timedelta(days=1),
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> GitHubReleaseModel:
|
||||
"""Get new data."""
|
||||
try:
|
||||
response = await self.client.repos.releases.latest("arendst/Tasmota")
|
||||
if response.data is None:
|
||||
raise UpdateFailed("No data received")
|
||||
except (GitHubConnectionException, GitHubRatelimitException) as ex:
|
||||
# Expected/transient, just wrap as failure
|
||||
raise UpdateFailed(ex) from ex
|
||||
except GitHubException as ex:
|
||||
self.logger.exception("Unexpected GitHub exception")
|
||||
raise UpdateFailed(ex) from ex
|
||||
else:
|
||||
return response.data
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hatasmota"],
|
||||
"mqtt": ["tasmota/discovery/#"],
|
||||
"requirements": ["HATasmota==0.10.1"]
|
||||
"requirements": ["HATasmota==0.10.1", "aiogithubapi==26.0.0"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Update entity for Tasmota."""
|
||||
|
||||
import re
|
||||
|
||||
from homeassistant.components.update import (
|
||||
UpdateDeviceClass,
|
||||
UpdateEntity,
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import TasmotaLatestReleaseUpdateCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Tasmota update entities."""
|
||||
coordinator = TasmotaLatestReleaseUpdateCoordinator(hass, config_entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
devices = device_registry.devices.get_devices_for_config_entry_id(
|
||||
config_entry.entry_id
|
||||
)
|
||||
async_add_entities(TasmotaUpdateEntity(coordinator, device) for device in devices)
|
||||
|
||||
|
||||
class TasmotaUpdateEntity(UpdateEntity):
|
||||
"""Representation of a Tasmota update entity."""
|
||||
|
||||
_attr_device_class = UpdateDeviceClass.FIRMWARE
|
||||
_attr_name = "Firmware"
|
||||
_attr_title = "Tasmota firmware"
|
||||
_attr_supported_features = UpdateEntityFeature.RELEASE_NOTES
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: TasmotaLatestReleaseUpdateCoordinator,
|
||||
device_entry: DeviceEntry,
|
||||
) -> None:
|
||||
"""Initialize the Tasmota update entity."""
|
||||
self.coordinator = coordinator
|
||||
self.device_entry = device_entry
|
||||
self._attr_unique_id = f"{device_entry.id}_update"
|
||||
|
||||
@property
|
||||
def installed_version(self) -> str | None:
|
||||
"""Return the installed version."""
|
||||
return self.device_entry.sw_version # type:ignore[union-attr]
|
||||
|
||||
@property
|
||||
def latest_version(self) -> str:
|
||||
"""Return the latest version."""
|
||||
return self.coordinator.data.tag_name.removeprefix("v")
|
||||
|
||||
@property
|
||||
def release_url(self) -> str:
|
||||
"""Return the release URL."""
|
||||
return self.coordinator.data.html_url
|
||||
|
||||
@property
|
||||
def release_summary(self) -> str:
|
||||
"""Return the release summary."""
|
||||
return self.coordinator.data.name
|
||||
|
||||
def release_notes(self) -> str | None:
|
||||
"""Return the release notes."""
|
||||
if not self.coordinator.data.body:
|
||||
return None
|
||||
# Remove the picture tag, it uses relative URLs that won't work in the UI
|
||||
return re.sub(
|
||||
r"^<picture>.*?</picture>", "", self.coordinator.data.body, flags=re.DOTALL
|
||||
)
|
||||
Generated
+1
@@ -273,6 +273,7 @@ aioftp==0.21.3
|
||||
aioghost==0.4.16
|
||||
|
||||
# homeassistant.components.github
|
||||
# homeassistant.components.tasmota
|
||||
aiogithubapi==26.0.0
|
||||
|
||||
# homeassistant.components.guardian
|
||||
|
||||
@@ -1 +1,33 @@
|
||||
"""Tests for the Duco integration."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the full Duco integration for testing."""
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return config_entry
|
||||
|
||||
|
||||
async def setup_platform_integration(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
platforms: Sequence[Platform],
|
||||
) -> MockConfigEntry:
|
||||
"""Set up selected Duco platforms for testing."""
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.duco.PLATFORMS", list(platforms)):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return config_entry
|
||||
|
||||
@@ -23,6 +23,8 @@ from homeassistant.components.duco.const import DOMAIN
|
||||
from homeassistant.const import CONF_HOST
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_array_fixture
|
||||
|
||||
TEST_HOST = "192.168.1.100"
|
||||
@@ -159,112 +161,7 @@ def mock_lan_info() -> LanInfo:
|
||||
@pytest.fixture
|
||||
def mock_nodes() -> list[Node]:
|
||||
"""Return a list of nodes covering all supported types."""
|
||||
return [
|
||||
Node(
|
||||
node_id=1,
|
||||
general=NodeGeneralInfo(
|
||||
node_type="BOX",
|
||||
sub_type=1,
|
||||
network_type="VIRT",
|
||||
parent=0,
|
||||
asso=0,
|
||||
name="Living",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=NodeVentilationInfo(
|
||||
state="AUTO",
|
||||
time_state_remain=0,
|
||||
time_state_end=0,
|
||||
mode="AUTO",
|
||||
flow_lvl_tgt=0,
|
||||
),
|
||||
sensor=NodeSensorInfo(
|
||||
co2=None,
|
||||
iaq_co2=None,
|
||||
rh=None,
|
||||
iaq_rh=None,
|
||||
temp=27.9,
|
||||
),
|
||||
),
|
||||
Node(
|
||||
node_id=2,
|
||||
general=NodeGeneralInfo(
|
||||
node_type="UCCO2",
|
||||
sub_type=0,
|
||||
network_type="RF",
|
||||
parent=1,
|
||||
asso=1,
|
||||
name="Office CO2",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=NodeVentilationInfo(
|
||||
state="AUTO",
|
||||
time_state_remain=0,
|
||||
time_state_end=0,
|
||||
mode="-",
|
||||
flow_lvl_tgt=None,
|
||||
),
|
||||
sensor=NodeSensorInfo(
|
||||
co2=405,
|
||||
iaq_co2=80,
|
||||
rh=None,
|
||||
iaq_rh=None,
|
||||
temp=19.8,
|
||||
),
|
||||
),
|
||||
Node(
|
||||
node_id=113,
|
||||
general=NodeGeneralInfo(
|
||||
node_type="BSRH",
|
||||
sub_type=0,
|
||||
network_type="RF",
|
||||
parent=1,
|
||||
asso=1,
|
||||
name="Bathroom RH",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=NodeVentilationInfo(
|
||||
state="AUTO",
|
||||
time_state_remain=0,
|
||||
time_state_end=0,
|
||||
mode="-",
|
||||
flow_lvl_tgt=None,
|
||||
),
|
||||
sensor=NodeSensorInfo(
|
||||
co2=None,
|
||||
iaq_co2=None,
|
||||
rh=42.0,
|
||||
iaq_rh=85,
|
||||
temp=27.9,
|
||||
),
|
||||
),
|
||||
Node(
|
||||
node_id=50,
|
||||
general=NodeGeneralInfo(
|
||||
node_type="UCRH",
|
||||
sub_type=0,
|
||||
network_type="RF",
|
||||
parent=1,
|
||||
asso=1,
|
||||
name="Kitchen RH",
|
||||
identify=0,
|
||||
),
|
||||
ventilation=NodeVentilationInfo(
|
||||
state="AUTO",
|
||||
time_state_remain=0,
|
||||
time_state_end=0,
|
||||
mode="-",
|
||||
flow_lvl_tgt=None,
|
||||
),
|
||||
sensor=NodeSensorInfo(
|
||||
co2=None,
|
||||
iaq_co2=None,
|
||||
rh=61.0,
|
||||
iaq_rh=90,
|
||||
temp=22.5,
|
||||
),
|
||||
),
|
||||
]
|
||||
return load_nodes_fixture("nodes.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -327,7 +224,4 @@ async def init_integration(
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Duco integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
return await setup_integration(hass, mock_config_entry)
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
[
|
||||
{
|
||||
"node_id": 1,
|
||||
"general": {
|
||||
"node_type": "BOX",
|
||||
"sub_type": 1,
|
||||
"network_type": "VIRT",
|
||||
"parent": 0,
|
||||
"asso": 0,
|
||||
"name": "Living",
|
||||
"identify": 0
|
||||
},
|
||||
"ventilation": {
|
||||
"state": "AUTO",
|
||||
"time_state_remain": 0,
|
||||
"time_state_end": 0,
|
||||
"mode": "AUTO",
|
||||
"flow_lvl_tgt": 0
|
||||
},
|
||||
"sensor": {
|
||||
"co2": null,
|
||||
"iaq_co2": null,
|
||||
"rh": null,
|
||||
"iaq_rh": null,
|
||||
"temp": 27.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"node_id": 2,
|
||||
"general": {
|
||||
"node_type": "UCCO2",
|
||||
"sub_type": 0,
|
||||
"network_type": "RF",
|
||||
"parent": 1,
|
||||
"asso": 1,
|
||||
"name": "Office CO2",
|
||||
"identify": 0
|
||||
},
|
||||
"ventilation": {
|
||||
"state": "AUTO",
|
||||
"time_state_remain": 0,
|
||||
"time_state_end": 0,
|
||||
"mode": "-",
|
||||
"flow_lvl_tgt": null
|
||||
},
|
||||
"sensor": {
|
||||
"co2": 405,
|
||||
"iaq_co2": 80,
|
||||
"rh": null,
|
||||
"iaq_rh": null,
|
||||
"temp": 19.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"node_id": 113,
|
||||
"general": {
|
||||
"node_type": "BSRH",
|
||||
"sub_type": 0,
|
||||
"network_type": "RF",
|
||||
"parent": 1,
|
||||
"asso": 1,
|
||||
"name": "Bathroom RH",
|
||||
"identify": 0
|
||||
},
|
||||
"ventilation": {
|
||||
"state": "AUTO",
|
||||
"time_state_remain": 0,
|
||||
"time_state_end": 0,
|
||||
"mode": "-",
|
||||
"flow_lvl_tgt": null
|
||||
},
|
||||
"sensor": {
|
||||
"co2": null,
|
||||
"iaq_co2": null,
|
||||
"rh": 42.0,
|
||||
"iaq_rh": 85,
|
||||
"temp": 27.9
|
||||
}
|
||||
},
|
||||
{
|
||||
"node_id": 50,
|
||||
"general": {
|
||||
"node_type": "UCRH",
|
||||
"sub_type": 0,
|
||||
"network_type": "RF",
|
||||
"parent": 1,
|
||||
"asso": 1,
|
||||
"name": "Kitchen RH",
|
||||
"identify": 0
|
||||
},
|
||||
"ventilation": {
|
||||
"state": "AUTO",
|
||||
"time_state_remain": 0,
|
||||
"time_state_end": 0,
|
||||
"mode": "-",
|
||||
"flow_lvl_tgt": null
|
||||
},
|
||||
"sensor": {
|
||||
"co2": null,
|
||||
"iaq_co2": null,
|
||||
"rh": 61.0,
|
||||
"iaq_rh": 90,
|
||||
"temp": 22.5
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for the Duco fan platform."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from duco_connectivity import DucoConnectionError, DucoError, DucoRateLimitError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
@@ -21,6 +21,8 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_platform_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
_FAN_ENTITY = "fan.living"
|
||||
@@ -33,11 +35,7 @@ async def init_integration(
|
||||
mock_duco_client: AsyncMock,
|
||||
) -> MockConfigEntry:
|
||||
"""Set up only the fan platform for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.duco.PLATFORMS", [Platform.FAN]):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
return await setup_platform_integration(hass, mock_config_entry, [Platform.FAN])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for the Duco sensor platform."""
|
||||
|
||||
import logging
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from duco_connectivity import (
|
||||
DucoConnectionError,
|
||||
@@ -22,6 +22,8 @@ from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
|
||||
from . import setup_platform_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@@ -34,11 +36,7 @@ async def init_integration(
|
||||
) -> MockConfigEntry:
|
||||
"""Set up only the sensor platform for testing."""
|
||||
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.duco.PLATFORMS", [Platform.SENSOR]):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
return await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
|
||||
@@ -231,6 +231,43 @@ async def test_options_flow(
|
||||
assert result["data"] == {CONF_FALLBACK: CONST_OVERLAY_TADO_DEFAULT}
|
||||
|
||||
|
||||
async def test_show_progress_polling(
|
||||
hass: HomeAssistant,
|
||||
mock_tado_api: MagicMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test progress step re-entry while login task is still running."""
|
||||
|
||||
event = threading.Event()
|
||||
|
||||
def mock_tado_api_device_activation() -> None:
|
||||
event.wait(timeout=5)
|
||||
|
||||
mock_tado_api.device_activation = mock_tado_api_device_activation
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "user"
|
||||
assert result["description_placeholders"]["url"] is not None
|
||||
assert result["description_placeholders"]["code"] == "TEST"
|
||||
|
||||
# Poll again while task is still running — this re-enters async_step_user
|
||||
# with self.tado already set
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||
assert result["description_placeholders"]["url"] is not None
|
||||
assert result["description_placeholders"]["code"] == "TEST"
|
||||
|
||||
# Now complete the login
|
||||
event.set()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_homekit(hass: HomeAssistant, mock_tado_api: MagicMock) -> None:
|
||||
"""Test that we abort from homekit if tado is already setup."""
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Tests for the Tasmota update platform."""
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
from aiogithubapi import GitHubReleaseModel
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .test_common import DEFAULT_CONFIG
|
||||
|
||||
from tests.common import async_fire_mqtt_message
|
||||
from tests.typing import MqttMockHAClient
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("candidate_version", "update_available"),
|
||||
[
|
||||
("0.0.0", False),
|
||||
(".".join(str(int(x) + 1) for x in DEFAULT_CONFIG["sw"].split(".")), True),
|
||||
],
|
||||
)
|
||||
async def test_update_state(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock: MqttMockHAClient,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
setup_tasmota,
|
||||
candidate_version: str,
|
||||
update_available: bool,
|
||||
) -> None:
|
||||
"""Test setting up a device."""
|
||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||
mac = config["mac"]
|
||||
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
f"{DEFAULT_PREFIX}/{mac}/config",
|
||||
json.dumps(config),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
device_entry = device_registry.async_get_device(
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, mac)}
|
||||
)
|
||||
|
||||
async def mock_latest(owner_repo):
|
||||
return GitHubReleaseModel(
|
||||
{
|
||||
"tag_name": f"v{candidate_version}",
|
||||
"name": f"Tasmota v{candidate_version} Foo",
|
||||
"html_url": f"https://github.com/arendst/Tasmota/releases/tag/v{candidate_version}",
|
||||
"body": """\
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="./tools/logo/TASMOTA_FullLogo_Vector_White.svg">
|
||||
<img alt="Logo" src="./tools/logo/TASMOTA_FullLogo_Vector.svg" align="right" height="76">
|
||||
</picture>
|
||||
|
||||
# RELEASE NOTES
|
||||
|
||||
... """,
|
||||
}
|
||||
)
|
||||
|
||||
# TODO mock the coordinator
|
||||
coordinator = hass.data["tasmota"]["coordinator"]
|
||||
coordinator.client.repos.releases.latest = mock_latest
|
||||
|
||||
# TODO update_available test, device_entry.sw_version has the current version
|
||||
Reference in New Issue
Block a user