hass.tibber_prices/developer/assets/js/ca2d854d.270f6d36.js
github-actions[bot] e9aea64a2e deploy: 6898c126e3
2025-12-06 01:42:39 +00:00

1 line
No EOL
28 KiB
JavaScript

"use strict";(globalThis.webpackChunkdocs_split_developer=globalThis.webpackChunkdocs_split_developer||[]).push([[7192],{5515:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>d,contentTitle:()=>c,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>a});const r=JSON.parse('{"id":"critical-patterns","title":"Critical Behavior Patterns - Testing Guide","description":"Purpose: This documentation lists essential behavior patterns that must be tested to ensure production-quality code and prevent resource leaks.","source":"@site/docs/critical-patterns.md","sourceDirName":".","slug":"/critical-patterns","permalink":"/hass.tibber_prices/developer/critical-patterns","draft":false,"unlisted":false,"editUrl":"https://github.com/jpawlowski/hass.tibber_prices/tree/main/docs/developer/docs/critical-patterns.md","tags":[],"version":"current","lastUpdatedAt":1764985026000,"frontMatter":{"comments":false},"sidebar":"tutorialSidebar","previous":{"title":"Coding Guidelines","permalink":"/hass.tibber_prices/developer/coding-guidelines"},"next":{"title":"Debugging Guide","permalink":"/hass.tibber_prices/developer/debugging"}}');var i=s(4848),t=s(8453);const l={comments:!1},c="Critical Behavior Patterns - Testing Guide",d={},a=[{value:"\ud83c\udfaf Why Are These Tests Critical?",id:"-why-are-these-tests-critical",level:2},{value:"\u2705 Test Categories",id:"-test-categories",level:2},{value:"1. Resource Cleanup (Memory Leak Prevention)",id:"1-resource-cleanup-memory-leak-prevention",level:3},{value:"1.1 Listener Cleanup \u2705",id:"11-listener-cleanup-",level:4},{value:"1.2 Timer Cleanup \u2705",id:"12-timer-cleanup-",level:4},{value:"1.3 Config Entry Cleanup \u2705",id:"13-config-entry-cleanup-",level:4},{value:"2. Cache Invalidation \u2705",id:"2-cache-invalidation-",level:3},{value:"2.1 Config Cache Invalidation",id:"21-config-cache-invalidation",level:4},{value:"3. Storage Cleanup \u2705",id:"3-storage-cleanup-",level:3},{value:"3.1 Persistent Storage Removal",id:"31-persistent-storage-removal",level:4},{value:"4. Timer Scheduling \u2705",id:"4-timer-scheduling-",level:3},{value:"5. Sensor-to-Timer Assignment \u2705",id:"5-sensor-to-timer-assignment-",level:3},{value:"\ud83d\udea8 Additional Analysis (Nice-to-Have Patterns)",id:"-additional-analysis-nice-to-have-patterns",level:2},{value:"6. Async Task Management",id:"6-async-task-management",level:3},{value:"7. API Session Cleanup",id:"7-api-session-cleanup",level:3},{value:"8. Translation Cache Memory",id:"8-translation-cache-memory",level:3},{value:"9. Coordinator Data Structure Integrity",id:"9-coordinator-data-structure-integrity",level:3},{value:"10. Service Response Memory",id:"10-service-response-memory",level:3},{value:"\ud83d\udcca Test Coverage Status",id:"-test-coverage-status",level:2},{value:"\u2705 Implemented Tests (41 total)",id:"-implemented-tests-41-total",level:3},{value:"\ud83d\udccb Analyzed but Not Implemented (Nice-to-Have)",id:"-analyzed-but-not-implemented-nice-to-have",level:3},{value:"\ud83c\udfaf Development Status",id:"-development-status",level:2},{value:"\u2705 All Critical Patterns Tested",id:"-all-critical-patterns-tested",level:3},{value:"\ud83d\udccb Nice-to-Have Tests (Optional)",id:"-nice-to-have-tests-optional",level:3},{value:"\ud83d\udd0d How to Run Tests",id:"-how-to-run-tests",level:2},{value:"\ud83d\udcda References",id:"-references",level:2}];function o(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"critical-behavior-patterns---testing-guide",children:"Critical Behavior Patterns - Testing Guide"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Purpose:"})," This documentation lists essential behavior patterns that must be tested to ensure production-quality code and prevent resource leaks."]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Last Updated:"})," 2025-11-22\n",(0,i.jsx)(n.strong,{children:"Test Coverage:"})," 41 tests implemented (100% of critical patterns)"]}),"\n",(0,i.jsx)(n.h2,{id:"-why-are-these-tests-critical",children:"\ud83c\udfaf Why Are These Tests Critical?"}),"\n",(0,i.jsxs)(n.p,{children:["Home Assistant integrations run ",(0,i.jsx)(n.strong,{children:"continuously"})," in the background. Resource leaks lead to:"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Memory Leaks"}),": RAM usage grows over days/weeks until HA becomes unstable"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Callback Leaks"}),": Listeners remain registered after entity removal \u2192 CPU load increases"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Timer Leaks"}),": Timers continue running after unload \u2192 unnecessary background tasks"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"File Handle Leaks"}),": Storage files remain open \u2192 system resources exhausted"]}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"-test-categories",children:"\u2705 Test Categories"}),"\n",(0,i.jsx)(n.h3,{id:"1-resource-cleanup-memory-leak-prevention",children:"1. Resource Cleanup (Memory Leak Prevention)"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"File:"})," ",(0,i.jsx)(n.code,{children:"tests/test_resource_cleanup.py"})]}),"\n",(0,i.jsx)(n.h4,{id:"11-listener-cleanup-",children:"1.1 Listener Cleanup \u2705"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Time-sensitive listeners are correctly removed (",(0,i.jsx)(n.code,{children:"async_add_time_sensitive_listener()"}),")"]}),"\n",(0,i.jsxs)(n.li,{children:["Minute-update listeners are correctly removed (",(0,i.jsx)(n.code,{children:"async_add_minute_update_listener()"}),")"]}),"\n",(0,i.jsxs)(n.li,{children:["Lifecycle callbacks are correctly unregistered (",(0,i.jsx)(n.code,{children:"register_lifecycle_callback()"}),")"]}),"\n",(0,i.jsx)(n.li,{children:"Sensor cleanup removes ALL registered listeners"}),"\n",(0,i.jsx)(n.li,{children:"Binary sensor cleanup removes ALL registered listeners"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Each registered listener holds references to Entity + Coordinator"}),"\n",(0,i.jsx)(n.li,{children:"Without cleanup: Entities are not freed by GC \u2192 Memory Leak"}),"\n",(0,i.jsx)(n.li,{children:"With 80+ sensors \xd7 3 listener types = 240+ callbacks that must be cleanly removed"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Code Locations:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/listeners.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_add_time_sensitive_listener()"}),", ",(0,i.jsx)(n.code,{children:"async_add_minute_update_listener()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"register_lifecycle_callback()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"sensor/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_will_remove_from_hass()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"binary_sensor/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_will_remove_from_hass()"})]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"12-timer-cleanup-",children:"1.2 Timer Cleanup \u2705"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Quarter-hour timer is cancelled and reference cleared"}),"\n",(0,i.jsx)(n.li,{children:"Minute timer is cancelled and reference cleared"}),"\n",(0,i.jsx)(n.li,{children:"Both timers are cancelled together"}),"\n",(0,i.jsxs)(n.li,{children:["Cleanup works even when timers are ",(0,i.jsx)(n.code,{children:"None"})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Uncancelled timers continue running after integration unload"}),"\n",(0,i.jsxs)(n.li,{children:["HA's ",(0,i.jsx)(n.code,{children:"async_track_utc_time_change()"})," creates persistent callbacks"]}),"\n",(0,i.jsx)(n.li,{children:"Without cleanup: Timers keep firing \u2192 CPU load + unnecessary coordinator updates"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Code Locations:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/listeners.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"cancel_timers()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_shutdown()"})]}),"\n"]}),"\n",(0,i.jsx)(n.h4,{id:"13-config-entry-cleanup-",children:"1.3 Config Entry Cleanup \u2705"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["Options update listener is registered via ",(0,i.jsx)(n.code,{children:"async_on_unload()"})]}),"\n",(0,i.jsxs)(n.li,{children:["Cleanup function is correctly passed to ",(0,i.jsx)(n.code,{children:"async_on_unload()"})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"entry.add_update_listener()"})," registers permanent callback"]}),"\n",(0,i.jsxs)(n.li,{children:["Without ",(0,i.jsx)(n.code,{children:"async_on_unload()"}),": Listener remains active after reload \u2192 duplicate updates"]}),"\n",(0,i.jsxs)(n.li,{children:["Pattern: ",(0,i.jsx)(n.code,{children:"entry.async_on_unload(entry.add_update_listener(handler))"})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Code Locations:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"__init__()"})," (listener registration)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"__init__.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_unload_entry()"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"2-cache-invalidation-",children:"2. Cache Invalidation \u2705"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"File:"})," ",(0,i.jsx)(n.code,{children:"tests/test_resource_cleanup.py"})]}),"\n",(0,i.jsx)(n.h4,{id:"21-config-cache-invalidation",children:"2.1 Config Cache Invalidation"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"DataTransformer config cache is invalidated on options change"}),"\n",(0,i.jsx)(n.li,{children:"PeriodCalculator config + period cache is invalidated"}),"\n",(0,i.jsx)(n.li,{children:"Trend calculator cache is cleared on coordinator update"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Stale config \u2192 Sensors use old user settings"}),"\n",(0,i.jsx)(n.li,{children:"Stale period cache \u2192 Incorrect best/peak price periods"}),"\n",(0,i.jsx)(n.li,{children:"Stale trend cache \u2192 Outdated trend analysis"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Code Locations:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/data_transformation.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"invalidate_config_cache()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/periods.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"invalidate_config_cache()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"sensor/calculators/trend.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"clear_trend_cache()"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"3-storage-cleanup-",children:"3. Storage Cleanup \u2705"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"File:"})," ",(0,i.jsx)(n.code,{children:"tests/test_resource_cleanup.py"})," + ",(0,i.jsx)(n.code,{children:"tests/test_coordinator_shutdown.py"})]}),"\n",(0,i.jsx)(n.h4,{id:"31-persistent-storage-removal",children:"3.1 Persistent Storage Removal"}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Storage file is deleted on config entry removal"}),"\n",(0,i.jsx)(n.li,{children:"Cache is saved on shutdown (no data loss)"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Without storage removal: Old files remain after uninstallation"}),"\n",(0,i.jsx)(n.li,{children:"Without cache save on shutdown: Data loss on HA restart"}),"\n",(0,i.jsxs)(n.li,{children:["Storage path: ",(0,i.jsx)(n.code,{children:".storage/tibber_prices.{entry_id}"})]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Code Locations:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"__init__.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_remove_entry()"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/core.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"async_shutdown()"})]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"4-timer-scheduling-",children:"4. Timer Scheduling \u2705"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"File:"})," ",(0,i.jsx)(n.code,{children:"tests/test_timer_scheduling.py"})]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Quarter-hour timer is registered with correct parameters"}),"\n",(0,i.jsx)(n.li,{children:"Minute timer is registered with correct parameters"}),"\n",(0,i.jsx)(n.li,{children:"Timers can be re-scheduled (override old timer)"}),"\n",(0,i.jsx)(n.li,{children:"Midnight turnover detection works correctly"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Wrong timer parameters \u2192 Entities update at wrong times"}),"\n",(0,i.jsx)(n.li,{children:"Without timer override on re-schedule \u2192 Multiple parallel timers \u2192 Performance problem"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"5-sensor-to-timer-assignment-",children:"5. Sensor-to-Timer Assignment \u2705"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"File:"})," ",(0,i.jsx)(n.code,{children:"tests/test_sensor_timer_assignment.py"})]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"What is tested:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:["All ",(0,i.jsx)(n.code,{children:"TIME_SENSITIVE_ENTITY_KEYS"})," are valid entity keys"]}),"\n",(0,i.jsxs)(n.li,{children:["All ",(0,i.jsx)(n.code,{children:"MINUTE_UPDATE_ENTITY_KEYS"})," are valid entity keys"]}),"\n",(0,i.jsx)(n.li,{children:"Both lists are disjoint (no overlap)"}),"\n",(0,i.jsx)(n.li,{children:"Sensor and binary sensor platforms are checked"}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why critical:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Wrong timer assignment \u2192 Sensors update at wrong times"}),"\n",(0,i.jsx)(n.li,{children:"Overlap \u2192 Duplicate updates \u2192 Performance problem"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"-additional-analysis-nice-to-have-patterns",children:"\ud83d\udea8 Additional Analysis (Nice-to-Have Patterns)"}),"\n",(0,i.jsxs)(n.p,{children:["These patterns were analyzed and classified as ",(0,i.jsx)(n.strong,{children:"not critical"}),":"]}),"\n",(0,i.jsx)(n.h3,{id:"6-async-task-management",children:"6. Async Task Management"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Current Status:"})," Fire-and-forget pattern for short tasks"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"sensor/core.py"})," \u2192 Chart data refresh (short-lived, max 1-2 seconds)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"coordinator/core.py"})," \u2192 Cache storage (short-lived, max 100ms)"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Why no tests needed:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"No long-running tasks (all < 2 seconds)"}),"\n",(0,i.jsx)(n.li,{children:"HA's event loop handles short tasks automatically"}),"\n",(0,i.jsx)(n.li,{children:"Task exceptions are already logged"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"If needed:"})," ",(0,i.jsx)(n.code,{children:"_chart_refresh_task"})," tracking + cancel in ",(0,i.jsx)(n.code,{children:"async_will_remove_from_hass()"})]}),"\n",(0,i.jsx)(n.h3,{id:"7-api-session-cleanup",children:"7. API Session Cleanup"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Current Status:"})," \u2705 Correctly implemented"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"async_get_clientsession(hass)"})," is used (shared session)"]}),"\n",(0,i.jsx)(n.li,{children:"No new sessions are created"}),"\n",(0,i.jsx)(n.li,{children:"HA manages session lifecycle automatically"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Code:"})," ",(0,i.jsx)(n.code,{children:"api/client.py"})," + ",(0,i.jsx)(n.code,{children:"__init__.py"})]}),"\n",(0,i.jsx)(n.h3,{id:"8-translation-cache-memory",children:"8. Translation Cache Memory"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Current Status:"})," \u2705 Bounded cache"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Max ~5-10 languages \xd7 5KB = 50KB total"}),"\n",(0,i.jsx)(n.li,{children:"Module-level cache without re-loading"}),"\n",(0,i.jsx)(n.li,{children:"Practically no memory issue"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Code:"})," ",(0,i.jsx)(n.code,{children:"const.py"})," \u2192 ",(0,i.jsx)(n.code,{children:"_TRANSLATIONS_CACHE"}),", ",(0,i.jsx)(n.code,{children:"_STANDARD_TRANSLATIONS_CACHE"})]}),"\n",(0,i.jsx)(n.h3,{id:"9-coordinator-data-structure-integrity",children:"9. Coordinator Data Structure Integrity"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Current Status:"})," Manually tested via ",(0,i.jsx)(n.code,{children:"./scripts/develop"})]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"Midnight turnover works correctly (observed over several days)"}),"\n",(0,i.jsxs)(n.li,{children:["Missing keys are handled via ",(0,i.jsx)(n.code,{children:".get()"})," with defaults"]}),"\n",(0,i.jsxs)(n.li,{children:["80+ sensors access ",(0,i.jsx)(n.code,{children:"coordinator.data"})," without errors"]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Structure:"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-python",children:'coordinator.data = {\n "user_data": {...},\n "priceInfo": [...], # Flat list of all enriched intervals\n "currency": "EUR" # Top-level for easy access\n}\n'})}),"\n",(0,i.jsx)(n.h3,{id:"10-service-response-memory",children:"10. Service Response Memory"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Current Status:"})," HA's response lifecycle"]}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"HA automatically frees service responses after return"}),"\n",(0,i.jsx)(n.li,{children:"ApexCharts ~20KB response is one-time per call"}),"\n",(0,i.jsx)(n.li,{children:"No response accumulation in integration code"}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Code:"})," ",(0,i.jsx)(n.code,{children:"services/apexcharts.py"})]}),"\n",(0,i.jsx)(n.h2,{id:"-test-coverage-status",children:"\ud83d\udcca Test Coverage Status"}),"\n",(0,i.jsx)(n.h3,{id:"-implemented-tests-41-total",children:"\u2705 Implemented Tests (41 total)"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Category"}),(0,i.jsx)(n.th,{children:"Status"}),(0,i.jsx)(n.th,{children:"Tests"}),(0,i.jsx)(n.th,{children:"File"}),(0,i.jsx)(n.th,{children:"Coverage"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Listener Cleanup"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"5"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_resource_cleanup.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Timer Cleanup"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"4"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_resource_cleanup.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Config Entry Cleanup"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"1"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_resource_cleanup.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Cache Invalidation"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"3"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_resource_cleanup.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Storage Cleanup"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"1"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_resource_cleanup.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Storage Persistence"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"2"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_coordinator_shutdown.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Timer Scheduling"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"8"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_timer_scheduling.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Sensor-Timer Assignment"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"17"}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"test_sensor_timer_assignment.py"})}),(0,i.jsx)(n.td,{children:"100%"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"TOTAL"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"\u2705"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"41"})}),(0,i.jsx)(n.td,{}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.strong,{children:"100% (critical)"})})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"-analyzed-but-not-implemented-nice-to-have",children:"\ud83d\udccb Analyzed but Not Implemented (Nice-to-Have)"}),"\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Category"}),(0,i.jsx)(n.th,{children:"Status"}),(0,i.jsx)(n.th,{children:"Rationale"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Async Task Management"}),(0,i.jsx)(n.td,{children:"\ud83d\udccb"}),(0,i.jsx)(n.td,{children:"Fire-and-forget pattern used (no long-running tasks)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"API Session Cleanup"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsxs)(n.td,{children:["Pattern correct (",(0,i.jsx)(n.code,{children:"async_get_clientsession"})," used)"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Translation Cache"}),(0,i.jsx)(n.td,{children:"\u2705"}),(0,i.jsx)(n.td,{children:"Cache size bounded (~50KB max for 10 languages)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Data Structure Integrity"}),(0,i.jsx)(n.td,{children:"\ud83d\udccb"}),(0,i.jsx)(n.td,{children:"Would add test time without finding real issues"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:"Service Response Memory"}),(0,i.jsx)(n.td,{children:"\ud83d\udccb"}),(0,i.jsx)(n.td,{children:"HA automatically frees service responses"})]})]})]}),"\n",(0,i.jsx)(n.p,{children:(0,i.jsx)(n.strong,{children:"Legend:"})}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"\u2705 = Fully tested or pattern verified correct"}),"\n",(0,i.jsx)(n.li,{children:"\ud83d\udccb = Analyzed, low priority for testing (no known issues)"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"-development-status",children:"\ud83c\udfaf Development Status"}),"\n",(0,i.jsx)(n.h3,{id:"-all-critical-patterns-tested",children:"\u2705 All Critical Patterns Tested"}),"\n",(0,i.jsx)(n.p,{children:"All essential memory leak prevention patterns are covered by 41 tests:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsx)(n.li,{children:"\u2705 Listeners are correctly removed (no callback leaks)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Timers are cancelled (no background task leaks)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Config entry cleanup works (no dangling listeners)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Caches are invalidated (no stale data issues)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Storage is saved and cleaned up (no data loss)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Timer scheduling works correctly (no update issues)"}),"\n",(0,i.jsx)(n.li,{children:"\u2705 Sensor-timer assignment is correct (no wrong updates)"}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"-nice-to-have-tests-optional",children:"\ud83d\udccb Nice-to-Have Tests (Optional)"}),"\n",(0,i.jsx)(n.p,{children:"If problems arise in the future, these tests can be added:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Async Task Management"})," - Pattern analyzed (fire-and-forget for short tasks)"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Data Structure Integrity"})," - Midnight rotation manually tested"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Service Response Memory"})," - HA's response lifecycle automatic"]}),"\n"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.strong,{children:"Conclusion:"})," The integration has production-quality test coverage for all critical resource leak patterns."]}),"\n",(0,i.jsx)(n.h2,{id:"-how-to-run-tests",children:"\ud83d\udd0d How to Run Tests"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"# Run all resource cleanup tests (14 tests)\n./scripts/test tests/test_resource_cleanup.py -v\n\n# Run all critical pattern tests (41 tests)\n./scripts/test tests/test_resource_cleanup.py tests/test_coordinator_shutdown.py \\\n tests/test_timer_scheduling.py tests/test_sensor_timer_assignment.py -v\n\n# Run all tests with coverage\n./scripts/test --cov=custom_components.tibber_prices --cov-report=html\n\n# Type checking and linting\n./scripts/check\n\n# Manual memory leak test\n# 1. Start HA: ./scripts/develop\n# 2. Monitor RAM: watch -n 1 'ps aux | grep home-assistant'\n# 3. Reload integration multiple times (HA UI: Settings \u2192 Devices \u2192 Tibber Prices \u2192 Reload)\n# 4. RAM should stabilize (not grow continuously)\n"})}),"\n",(0,i.jsx)(n.h2,{id:"-references",children:"\ud83d\udcda References"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Home Assistant Cleanup Patterns"}),": ",(0,i.jsx)(n.a,{href:"https://developers.home-assistant.io/docs/integration_setup_failures/#cleanup",children:"https://developers.home-assistant.io/docs/integration_setup_failures/#cleanup"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Async Best Practices"}),": ",(0,i.jsx)(n.a,{href:"https://developers.home-assistant.io/docs/asyncio_101/",children:"https://developers.home-assistant.io/docs/asyncio_101/"})]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Memory Profiling"}),": ",(0,i.jsx)(n.a,{href:"https://docs.python.org/3/library/tracemalloc.html",children:"https://docs.python.org/3/library/tracemalloc.html"})]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(o,{...e})}):o(e)}}}]);