---
comments: false
---

# Critical Behavior Patterns - Testing Guide

**Purpose:** This documentation lists essential behavior patterns that must be tested to ensure production-quality code and prevent resource leaks.

**Last Updated:** 2025-11-22
**Test Coverage:** 41 tests implemented (100% of critical patterns)

## 🎯 Why Are These Tests Critical?

Home Assistant integrations run **continuously** in the background. Resource leaks lead to:

- **Memory Leaks**: RAM usage grows over days/weeks until HA becomes unstable
- **Callback Leaks**: Listeners remain registered after entity removal → CPU load increases
- **Timer Leaks**: Timers continue running after unload → unnecessary background tasks
- **File Handle Leaks**: Storage files remain open → system resources exhausted

## ✅ Test Categories

### 1. Resource Cleanup (Memory Leak Prevention)

**File:** `tests/test_resource_cleanup.py`

#### 1.1 Listener Cleanup ✅

**What is tested:**

- Time-sensitive listeners are correctly removed (`async_add_time_sensitive_listener()`)
- Minute-update listeners are correctly removed (`async_add_minute_update_listener()`)
- Lifecycle callbacks are correctly unregistered (`register_lifecycle_callback()`)
- Sensor cleanup removes ALL registered listeners
- Binary sensor cleanup removes ALL registered listeners

**Why critical:**

- Each registered listener holds references to Entity + Coordinator
- Without cleanup: Entities are not freed by GC → Memory Leak
- With 80+ sensors × 3 listener types = 240+ callbacks that must be cleanly removed

**Code Locations:**

- `coordinator/listeners.py` → `async_add_time_sensitive_listener()`, `async_add_minute_update_listener()`
- `coordinator/core.py` → `register_lifecycle_callback()`
- `sensor/core.py` → `async_will_remove_from_hass()`
- `binary_sensor/core.py` → `async_will_remove_from_hass()`

#### 1.2 Timer Cleanup ✅

**What is tested:**

- Quarter-hour timer is cancelled and reference cleared
- Minute timer is cancelled and reference cleared
- Both timers are cancelled together
- Cleanup works even when timers are `None`

**Why critical:**

- Uncancelled timers continue running after integration unload
- HA's `async_track_utc_time_change()` creates persistent callbacks
- Without cleanup: Timers keep firing → CPU load + unnecessary coordinator updates

**Code Locations:**

- `coordinator/listeners.py` → `cancel_timers()`
- `coordinator/core.py` → `async_shutdown()`

#### 1.3 Config Entry Cleanup ✅

**What is tested:**

- Options update listener is registered via `async_on_unload()`
- Cleanup function is correctly passed to `async_on_unload()`

**Why critical:**

- `entry.add_update_listener()` registers permanent callback
- Without `async_on_unload()`: Listener remains active after reload → duplicate updates
- Pattern: `entry.async_on_unload(entry.add_update_listener(handler))`

**Code Locations:**

- `coordinator/core.py` → `__init__()` (listener registration)
- `__init__.py` → `async_unload_entry()`

### 2. Cache Invalidation ✅

**File:** `tests/test_resource_cleanup.py`

#### 2.1 Config Cache Invalidation

**What is tested:**

- DataTransformer config cache is invalidated on options change
- PeriodCalculator config + period cache is invalidated
- Trend calculator cache is cleared on coordinator update

**Why critical:**

- Stale config → Sensors use old user settings
- Stale period cache → Incorrect best/peak price periods
- Stale trend cache → Outdated trend analysis

**Code Locations:**

- `coordinator/data_transformation.py` → `invalidate_config_cache()`
- `coordinator/periods.py` → `invalidate_config_cache()`
- `sensor/calculators/trend.py` → `clear_trend_cache()`

### 3. Storage Cleanup ✅

**File:** `tests/test_resource_cleanup.py` + `tests/test_coordinator_shutdown.py`

#### 3.1 Persistent Storage Removal

**What is tested:**

- Storage file is deleted on config entry removal
- Cache is saved on shutdown (no data loss)

**Why critical:**

- Without storage removal: Old files remain after uninstallation
- Without cache save on shutdown: Data loss on HA restart
- Storage path: `.storage/tibber_prices.{entry_id}`

**Code Locations:**

- `__init__.py` → `async_remove_entry()`
- `coordinator/core.py` → `async_shutdown()`

### 4. Timer Scheduling ✅

**File:** `tests/test_timer_scheduling.py`

**What is tested:**

- Quarter-hour timer is registered with correct parameters
- Minute timer is registered with correct parameters
- Timers can be re-scheduled (override old timer)
- Midnight turnover detection works correctly

**Why critical:**

- Wrong timer parameters → Entities update at wrong times
- Without timer override on re-schedule → Multiple parallel timers → Performance problem

### 5. Sensor-to-Timer Assignment ✅

**File:** `tests/test_sensor_timer_assignment.py`

**What is tested:**

- All `TIME_SENSITIVE_ENTITY_KEYS` are valid entity keys
- All `MINUTE_UPDATE_ENTITY_KEYS` are valid entity keys
- Both lists are disjoint (no overlap)
- Sensor and binary sensor platforms are checked

**Why critical:**

- Wrong timer assignment → Sensors update at wrong times
- Overlap → Duplicate updates → Performance problem

## 🚨 Additional Analysis (Nice-to-Have Patterns)

These patterns were analyzed and classified as **not critical**:

### 6. Async Task Management

**Current Status:** Fire-and-forget pattern for short tasks

- `sensor/core.py` → Chart data refresh (short-lived, max 1-2 seconds)
- `coordinator/core.py` → Cache storage (short-lived, max 100ms)

**Why no tests needed:**

- No long-running tasks (all < 2 seconds)
- HA's event loop handles short tasks automatically
- Task exceptions are already logged

**If needed:** `_chart_refresh_task` tracking + cancel in `async_will_remove_from_hass()`

### 7. API Session Cleanup

**Current Status:** ✅ Correctly implemented

- `async_get_clientsession(hass)` is used (shared session)
- No new sessions are created
- HA manages session lifecycle automatically

**Code:** `api/client.py` + `__init__.py`

### 8. Translation Cache Memory

**Current Status:** ✅ Bounded cache

- Max ~5-10 languages × 5KB = 50KB total
- Module-level cache without re-loading
- Practically no memory issue

**Code:** `const.py` → `_TRANSLATIONS_CACHE`, `_STANDARD_TRANSLATIONS_CACHE`

### 9. Coordinator Data Structure Integrity

**Current Status:** Manually tested via `./scripts/develop`

- Midnight turnover works correctly (observed over several days)
- Missing keys are handled via `.get()` with defaults
- 80+ sensors access `coordinator.data` without errors

**Structure:**

```python
coordinator.data = {
    "user_data": {...},
    "priceInfo": [...],  # Flat list of all enriched intervals
    "currency": "EUR"  # Top-level for easy access
}
```

### 10. Service Response Memory

**Current Status:** HA's response lifecycle

- HA automatically frees service responses after return
- ApexCharts ~20KB response is one-time per call
- No response accumulation in integration code

**Code:** `services/apexcharts.py`

## 📊 Test Coverage Status

### ✅ Implemented Tests (41 total)

| Category                | Status | Tests  | File                              | Coverage            |
| ----------------------- | ------ | ------ | --------------------------------- | ------------------- |
| Listener Cleanup        | ✅     | 5      | `test_resource_cleanup.py`        | 100%                |
| Timer Cleanup           | ✅     | 4      | `test_resource_cleanup.py`        | 100%                |
| Config Entry Cleanup    | ✅     | 1      | `test_resource_cleanup.py`        | 100%                |
| Cache Invalidation      | ✅     | 3      | `test_resource_cleanup.py`        | 100%                |
| Storage Cleanup         | ✅     | 1      | `test_resource_cleanup.py`        | 100%                |
| Storage Persistence     | ✅     | 2      | `test_coordinator_shutdown.py`    | 100%                |
| Timer Scheduling        | ✅     | 8      | `test_timer_scheduling.py`        | 100%                |
| Sensor-Timer Assignment | ✅     | 17     | `test_sensor_timer_assignment.py` | 100%                |
| **TOTAL**               | **✅** | **41** |                                   | **100% (critical)** |

### 📋 Analyzed but Not Implemented (Nice-to-Have)

| Category                 | Status | Rationale                                            |
| ------------------------ | ------ | ---------------------------------------------------- |
| Async Task Management    | 📋     | Fire-and-forget pattern used (no long-running tasks) |
| API Session Cleanup      | ✅     | Pattern correct (`async_get_clientsession` used)     |
| Translation Cache        | ✅     | Cache size bounded (~50KB max for 10 languages)      |
| Data Structure Integrity | 📋     | Would add test time without finding real issues      |
| Service Response Memory  | 📋     | HA automatically frees service responses             |

**Legend:**

- ✅ = Fully tested or pattern verified correct
- 📋 = Analyzed, low priority for testing (no known issues)

## 🎯 Development Status

### ✅ All Critical Patterns Tested

All essential memory leak prevention patterns are covered by 41 tests:

- ✅ Listeners are correctly removed (no callback leaks)
- ✅ Timers are cancelled (no background task leaks)
- ✅ Config entry cleanup works (no dangling listeners)
- ✅ Caches are invalidated (no stale data issues)
- ✅ Storage is saved and cleaned up (no data loss)
- ✅ Timer scheduling works correctly (no update issues)
- ✅ Sensor-timer assignment is correct (no wrong updates)

### 📋 Nice-to-Have Tests (Optional)

If problems arise in the future, these tests can be added:

1. **Async Task Management** - Pattern analyzed (fire-and-forget for short tasks)
2. **Data Structure Integrity** - Midnight rotation manually tested
3. **Service Response Memory** - HA's response lifecycle automatic

**Conclusion:** The integration has production-quality test coverage for all critical resource leak patterns.

## 🔍 How to Run Tests

```bash
# Run all resource cleanup tests (14 tests)
./scripts/test tests/test_resource_cleanup.py -v

# Run all critical pattern tests (41 tests)
./scripts/test tests/test_resource_cleanup.py tests/test_coordinator_shutdown.py \
              tests/test_timer_scheduling.py tests/test_sensor_timer_assignment.py -v

# Run all tests with coverage
./scripts/test --cov=custom_components.tibber_prices --cov-report=html

# Type checking and linting
./scripts/check

# Manual memory leak test
# 1. Start HA: ./scripts/develop
# 2. Monitor RAM: watch -n 1 'ps aux | grep home-assistant'
# 3. Reload integration multiple times (HA UI: Settings → Devices → Tibber Prices → Reload)
# 4. RAM should stabilize (not grow continuously)
```

## 📚 References

- **Home Assistant Cleanup Patterns**: https://developers.home-assistant.io/docs/integration_setup_failures/#cleanup
- **Async Best Practices**: https://developers.home-assistant.io/docs/asyncio_101/
- **Memory Profiling**: https://docs.python.org/3/library/tracemalloc.html
