1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-17 23:53:49 +01:00

Fix Firefly iii sensors not updating (#165450)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
alorente
2026-03-24 16:01:11 +01:00
committed by GitHub
parent 031830f004
commit ac7b5a2957
5 changed files with 65 additions and 29 deletions

View File

@@ -36,10 +36,10 @@ DEFAULT_SCAN_INTERVAL = timedelta(minutes=5)
class FireflyCoordinatorData:
"""Data structure for Firefly III coordinator data."""
accounts: list[Account]
accounts: dict[str, Account]
categories: list[Category]
category_details: list[Category]
budgets: list[Budget]
category_details: dict[str, Category]
budgets: dict[str, Budget]
bills: list[Bill]
primary_currency: Currency
@@ -142,10 +142,10 @@ class FireflyDataUpdateCoordinator(DataUpdateCoordinator[FireflyCoordinatorData]
) from err
return FireflyCoordinatorData(
accounts=accounts,
accounts={account.id: account for account in accounts},
categories=categories,
category_details=category_details,
budgets=budgets,
category_details={category.id: category for category in category_details},
budgets={budget.id: budget for budget in budgets},
bills=bills,
primary_currency=primary_currency,
)

View File

@@ -44,7 +44,7 @@ class FireflyAccountBaseEntity(FireflyBaseEntity):
) -> None:
"""Initialize a Firefly account entity."""
super().__init__(coordinator)
self._account = account
self._account_id = account.id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
manufacturer=MANUFACTURER,
@@ -58,6 +58,10 @@ class FireflyAccountBaseEntity(FireflyBaseEntity):
f"{coordinator.config_entry.unique_id}_account_{account.id}_{key}"
)
@property
def _account(self) -> Account:
return self.coordinator.data.accounts[self._account_id]
class FireflyCategoryBaseEntity(FireflyBaseEntity):
"""Base class for Firefly III category entity."""
@@ -70,7 +74,7 @@ class FireflyCategoryBaseEntity(FireflyBaseEntity):
) -> None:
"""Initialize a Firefly category entity."""
super().__init__(coordinator)
self._category = category
self._category_id = category.id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
manufacturer=MANUFACTURER,
@@ -84,6 +88,10 @@ class FireflyCategoryBaseEntity(FireflyBaseEntity):
f"{coordinator.config_entry.unique_id}_category_{category.id}_{key}"
)
@property
def _category(self) -> Category:
return self.coordinator.data.category_details[self._category_id]
class FireflyBudgetBaseEntity(FireflyBaseEntity):
"""Base class for Firefly III budget entity."""
@@ -96,7 +104,7 @@ class FireflyBudgetBaseEntity(FireflyBaseEntity):
) -> None:
"""Initialize a Firefly budget entity."""
super().__init__(coordinator)
self._budget = budget
self._budget_id = budget.id
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
manufacturer=MANUFACTURER,
@@ -109,3 +117,7 @@ class FireflyBudgetBaseEntity(FireflyBaseEntity):
self._attr_unique_id = (
f"{coordinator.config_entry.unique_id}_budget_{budget.id}_{key}"
)
@property
def _budget(self) -> Budget:
return self.coordinator.data.budgets[self._budget_id]

View File

@@ -51,7 +51,7 @@ async def async_setup_entry(
coordinator = entry.runtime_data
entities: list[SensorEntity] = []
for account in coordinator.data.accounts:
for account in coordinator.data.accounts.values():
entities.append(
FireflyAccountBalanceSensor(coordinator, account, ACCOUNT_BALANCE)
)
@@ -61,14 +61,14 @@ async def async_setup_entry(
entities.extend(
[
FireflyCategorySensor(coordinator, category, CATEGORY)
for category in coordinator.data.category_details
for category in coordinator.data.category_details.values()
]
)
entities.extend(
[
FireflyBudgetSensor(coordinator, budget, BUDGET)
for budget in coordinator.data.budgets
for budget in coordinator.data.budgets.values()
]
)
@@ -90,7 +90,6 @@ class FireflyAccountBalanceSensor(FireflyAccountBaseEntity, SensorEntity):
) -> None:
"""Initialize the account balance sensor."""
super().__init__(coordinator, account, key)
self._account = account
self._attr_native_unit_of_measurement = (
coordinator.data.primary_currency.attributes.code
)
@@ -108,16 +107,6 @@ class FireflyAccountRoleSensor(FireflyAccountBaseEntity, SensorEntity):
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_entity_registry_enabled_default = True
def __init__(
self,
coordinator: FireflyDataUpdateCoordinator,
account: Account,
key: str,
) -> None:
"""Initialize the account role sensor."""
super().__init__(coordinator, account, key)
self._account = account
@property
def native_value(self) -> StateType:
"""Return account role."""
@@ -173,7 +162,6 @@ class FireflyCategorySensor(FireflyCategoryBaseEntity, SensorEntity):
) -> None:
"""Initialize the category sensor."""
super().__init__(coordinator, category, key)
self._category = category
self._attr_native_unit_of_measurement = (
coordinator.data.primary_currency.attributes.code
)
@@ -205,7 +193,6 @@ class FireflyBudgetSensor(FireflyBudgetBaseEntity, SensorEntity):
) -> None:
"""Initialize the budget sensor."""
super().__init__(coordinator, budget, key)
self._budget = budget
self._attr_native_unit_of_measurement = (
coordinator.data.primary_currency.attributes.code
)

View File

@@ -185,7 +185,7 @@
'options': dict({
}),
'original_device_class': None,
'original_icon': 'mdi:hand-coin',
'original_icon': 'mdi:cash-plus',
'original_name': 'Account Type',
'platform': 'firefly_iii',
'previous_unique_id': None,
@@ -200,14 +200,14 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Credit Card Account Type',
'icon': 'mdi:hand-coin',
'icon': 'mdi:cash-plus',
}),
'context': <ANY>,
'entity_id': 'sensor.credit_card_account_type',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'liability',
'state': 'revenue',
})
# ---
# name: test_all_entities[sensor.lunch_earned_spent-entry]

View File

@@ -1,5 +1,6 @@
"""Tests for the Firefly III sensor platform."""
from copy import deepcopy
from unittest.mock import AsyncMock, patch
from freezegun.api import FrozenDateTimeFactory
@@ -8,9 +9,11 @@ from pyfirefly.exceptions import (
FireflyConnectionError,
FireflyTimeoutError,
)
from pyfirefly.models import Budget
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.firefly_iii.const import DOMAIN
from homeassistant.components.firefly_iii.coordinator import DEFAULT_SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE, Platform
@@ -20,7 +23,12 @@ from homeassistant.util import dt as dt_util
from . import setup_integration
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
from tests.common import (
MockConfigEntry,
async_fire_time_changed,
async_load_json_array_fixture,
snapshot_platform,
)
async def test_all_entities(
@@ -69,3 +77,32 @@ async def test_refresh_exceptions(
state = hass.states.get("sensor.credit_card_account_balance")
assert state is not None
assert state.state == STATE_UNAVAILABLE
async def test_budget_sensor_updates_after_refresh(
hass: HomeAssistant,
mock_firefly_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test budget sensor tracks refreshed coordinator budget objects."""
await setup_integration(hass, mock_config_entry)
state = hass.states.get("sensor.bills_budget")
assert state is not None
assert state.state == "123.45"
updated_budget_data = await async_load_json_array_fixture(
hass, "budgets.json", DOMAIN
)
updated_budget = deepcopy(updated_budget_data[0])
updated_budget["attributes"]["spent"][0]["sum"] = "999.99"
mock_firefly_client.get_budgets.return_value = [Budget.from_dict(updated_budget)]
freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass, dt_util.utcnow())
await hass.async_block_till_done(wait_background_tasks=True)
updated_state = hass.states.get("sensor.bills_budget")
assert updated_state is not None
assert updated_state.state == "999.99"